Evaluating swift by porting a small C++ program

Here’s the original C++ program: Owen Lynn / meow · GitLab

And here’s the (mostly done) port in Swift: Owen Lynn / meowsw · GitLab

Java/Javascript/C# are what I call 2nd gen C-likes and Rust/Zig/Swift are what I call 3rd gen C-likes. I’ve now played with all three of these 3rd gen languages, and of the three, Swift comes out ahead IMHO. I may adopt it for future projects.

The only real complaint I have about the language itself is the lack of a defer statement. It’s not a dealbreaker for me but it does improve quality of life, IMHO. Sometimes you need to close handles before you exit the function and defer makes that happen without introducing errors.

The Foundation API is usable but I do question some of the design choices made. It feels a lot like it was made back in 1997 or so, forcing you to create URLs before opening files for reading, for instance. That and to get an actual pointer to a buffer you just read in - you need to do it in a closure. A closure.

Calling C libraries is a little rough - the modulemap file requires a full path to the C header, which probably doesn’t work on other systems (I’m on linux). But it was possible to get up and running, calling C code after an hour or two. I do appreciate the attitude that there’s other code out there in the world and that people do want to link and call it. Too many languages want you to stay in the walled garden. Thank you.

Overall, I think it took me about a day to get something printing identical output to the C++ program and maybe another day to sand off all the rough edges. And I liked when I guessed at what an expression would be, the guess was right more often than not. I didn’t feel like I was fighting the compiler the whole time.

3 Likes

Defer does exist in swift. You can just write:

func myFunc() {
  defer { print("after") }
  print("before")
}
4 Likes

It's great that you like Swift more than Zig or Rust. I think you'll get a lot of agreement around here. :grinning_face: You'll probably like it even more if you continue learning it. As @Michael_Gottesman mentioned, Swift has a defer statement, addressing your complaint.

Foundation is even older than you suggested, and was originally an Objective-C framework, and parts of it do strongly reflect that history. Here's the header comment from NSFileHandle.h:

/*	NSFileHandle.h
	Copyright (c) 1995-2019, Apple Inc. All rights reserved.
*/

You don't have to use Foundation for file I/O. You can use Unix APIs and the C standard library directly, for the most part, but you'll have to jump through hoops because Swift is much stricter and much more verbose about pointer conversions than C. You can get some convenience using Apple's swift-system package which wraps some Unix APIs.

You also don't have to use or wrap OpenSSL yourself for SHA256 because Apple also has a swift-crypto package.

Here's my version of your program, using the two packages I mentioned:

import Crypto
import SystemPackage

@main
struct Meow {
  static func main() {
    precondition(
      allWords.count >= SHA256Digest.byteCount,
      "built-in word list is shorter than a SHA256 digest"
    )

    let (path, fd) = openInput()

    var engine = SHA256()
    withUnsafeTemporaryAllocation(byteCount: 64 * 1024, alignment: 1) { buffer in
      while true {
        do {
          let count = try fd.read(into: buffer, retryOnInterrupt: true)
          guard count > 0 else { break }
          // Ugly hoop-jumping to get the raw buffer required by update.
          engine.update(bufferPointer: .init(rebasing: buffer[0 ..< count]))
        } catch {
          fatalError("\(path): \(error)")
        }
      }
    }

    let digest = engine.finalize()
    var words = allWords
    var answer = ""
    for byte in digest {
      let word = words.remove(at: Int(byte) % words.count)
      answer += word
    }
    print(answer)
  }
}

let allWords = [
  "Affectionate", "Annoyed", "Archy", "Bitey", "Boopable", "Bored",
  "Calico", "Cautious", "Chasey", "Chatty", "Clawy", "Clean", "Curious",
  "Cute", "Early", "Fangy", "Fast", "Furry", "Hairy", "Heavy", "Hidey",
  "Hungry", "Leathery", "Naughty", "Needy", "Neurotic", "Nibbleable",
  "Mackerel", "Maternal", "Mischievous", "Musky", "Perchy", "Pettable",
  "Playful", "Pointy", "Poopy", "Pukey", "Purry", "Quick", "Rough",
  "Scratchy", "Scritchable", "Sharp", "Sickey", "Silky", "Sleepy",
  "Smooth", "Snuggly", "Soft", "Stealthy", "Strokeable", "Sweet", "Tabby",
  "Tom", "Tickly", "Tortoiseshell", "Triangular", "Tuxedo", "Twitchy",
  "Warm", "Wet",
]

func openInput() -> (String, FileDescriptor) {
  guard let path = CommandLine.arguments.dropFirst().first
  else { return ("stdin", .standardInput) }
  do {
    return (path, try FileDescriptor.open(.init(path), .readOnly))
  } catch {
    fatalError("\(path): \(error)")
  }
}
2 Likes

Have I got news for you: it's older than that. (Mind you, it's been heavily refactored since then and the Swift interface is of course newer too. And URL specifically does not date to 1994, shockingly.)

5 Likes

...time to bring Swift to m68k and close the loop...

3 Likes

Not quite m68k, but close enough (MacOS 9 on PowerPC): Swift on Mac OS 9 // -dealloc

4 Likes

The tradeoff is that package isn’t cross-platform. What would send me over the moon is if I could do something like import cLibrary and get all the stdio and stdlib calls with as thin a wrapper around them as possible. You’re almost guaranteed a C library of some sort on just about everything, from a Raspberry Pi to a supercomputer. At least Foundation (with all its warts) is guaranteed to be cross-platform. If it’s not, let me know :stuck_out_tongue:

Sure, I could’ve used some native SHA256 library to do the heavy lifting, but that’s not the point of doing what I did. I wanted to see how easy (or hard or silly) it would be to call external C code. Sometimes all you have is a .h and and a .a and nothing more, and the company that made it all went out of business back in 2005. Sometimes it’s a huge C library that has been tested and debugged and rewriting it would be risky. Or it’s also being used to provide the same API to Python. I like swift’s attitude of “let’s interop with C++” instead of java’s “rewrite everything and stay in our walled garden”.

Finally, you could’ve put the whole thing in a closure. Then it would be perfect :stuck_out_tongue: I don’t object too much to closures but the more complicated you make the code, the less maintainable it is, or the more likely the next guy who has to maintain the code will want to rewrite it.

Has anyone tried using GTK3/4 with swift? How about ncurses? Those I would happily use someone else’s wrapper.

There is some difference in the API presented on Apple platforms vs elsewhere (Windows/Linux/FreeBSD), and unfortunately I don't think that's documented in the Apple Developer Documentation.

There is a wrapper around some of GTK's APIs in Swift used internally by swift-cross-ui (you can see the GTK 4 bindings in Sources/Gtk/ and the GTK 3 bindings in Sources/Gtk3/), though they're generated by an ad-hoc code generator (Sources/GtkCodeGen/GtkCodeGen.swift) and not really meant for external consumption. In any case, you can see them being used by the GtkExample and and Gtk3Example targets.

2 Likes

I built swift-newt wrapper for writing little TUI tools.

Having something pretty like Bubble Tea for Go would be nice.