top | item 46433894

(no title)

scottmf | 2 months ago

Concurrency issues aside, I've been working on a greenfield iOS project recently and I've really been enjoying much of Swift's syntax.

I’ve also been experimenting with Go on a separate project and keep running into the opposite feeling — a lot of relatively common code (fetching/decoding) seems to look so visually messy.

E.g., I find this Swift example from the article to be very clean:

    func fetchUser(id: Int) async throws -> User {
        let url = URL(string: "https://api.example.com/users/\(id)")!
        let (data, _) = try await URLSession.shared.data(from: url)
        return try JSONDecoder().decode(User.self, from: data)
    }

And in Go (roughly similar semantics)

    func fetchUser(ctx context.Context, client *http.Client, id int) (User, error) {
        req, err := http.NewRequestWithContext(
            ctx,
            http.MethodGet,
            fmt.Sprintf("https://api.example.com/users/%d", id),
            nil,
        )
        if err != nil {
            return User{}, err
        }
    
        resp, err := client.Do(req)
        if err != nil {
            return User{}, err
        }
        defer resp.Body.Close()
    
        var u User
        if err := json.NewDecoder(resp.Body).Decode(&u); err != nil {
            return User{}, err
        }
        return u, nil
    }

I understand why it's more verbose (a lot of things are more explicit by design), but it's still hard not to prefer the cleaner Swift example. The success path is just three straightforward lines in Swift. While the verbosity of Go effectively buries the key steps in the surrounding boilerplate.

This isn't to pick on Go or say Swift is a better language in practice — and certainly not in the same domains — but I do wish there were a strongly typed, compiled language with the maturity/performance of e.g. Go/Rust and a syntax a bit closer to Swift (or at least closer to how Swift feels in simple demos, or the honeymoon phase)

discuss

order

tarentel|2 months ago

As someone who has been coding production Swift since 1.0 the Go example is a lot more what Swift in practice will look like. I suppose there are advantages to being able to only show the important parts.

The first line won't crash but in practice it is fairly rare where you'd implicitly unwrap something like that. URLs might be the only case where it is somewhat safe. But a more fair example would be something like

    func fetchUser(id: Int) async throws -> User {
      guard let url = URL(string: "https://api.example.com/users/\(id)") else {
        throw MyError.invalidURL
      }
    
      // you'll pretty much never see data(url: ...) in real life
      let request = URLRequest(url: url)
      // configure request
      
      let (data, response) = try await URLSession.shared.data(for: request)
      guard let httpResponse = response as? HTTPURLResponse,
            200..<300 ~= httpResponse.statusCode else {
        throw MyError.invalidResponseCode
      }
    
      // possibly other things you'd want to check
    
      return try JSONDecoder().decode(User.self, from: data)
    }
I don't code in Go so I don't know how production ready that code is. What I posted has a lot of issues with it as well but it is much closer to what would need to be done as a start. The Swift example is hiding a lot of the error checking that Go forces you to do to some extent.

eptcyka|2 months ago

Oh, don't get me started on handling the same type of exception differently depending on what call actually threw the exception. I find the happy path in Swift to be lovely, but as soon as exceptions have to be handled or there's some details that have to be gotten right in terms of order of execution, everything turns into a massive mess.

Still better than the 3 lines of if err is not nil that go gets you to do though.

Jtsummers|2 months ago

I'm not familiar with Swift's libraries, but what's the point of making this two lines instead of one:

  let request = URLRequest(url: url)
  let (data, response) = try await URLSession.shared.data(for: request)

  // vs
  let (data, response) = try await URLSession.shared.data(from: url)
That aside, your Swift version is still about half the size of the Go version with similar levels of error handling.

scottmf|2 months ago

Thanks. What could possibly cause an invalid URL in this example though?

willtemperley|2 months ago

Yes swift-format will say "never force-unwrap" because it is a potential crash.

tidwall|2 months ago

Or this.

    func fetchUser(id int) (user User, err error) {
        resp, err := http.Get(fmt.Sprintf("https://api.example.com/users/%d", id))
        if err != nil {
            return user, err
        }
        defer resp.Body.Close()
        return user, json.NewDecoder(resp.Body).Decode(&user)
    }

jtbaker|2 months ago

I'm conflicted about the implicit named returns using this pattern in go. It's definitely tidier but I feel like the control flow is harder to follow: "I never defined `user` how can I return it?".

Also those variables are returned even if you don't explicitly return them, which feels a little unintuitive.

neonsunset|2 months ago

C# :)

    async Task<User> FetchUser(int id, HttpClient http, CancellationToken token)
    {
        var addr = $"https://api.example.com/users/{id}";
        var user = await http.GetFromJsonAsync<User>(addr, token);
        return user ?? throw new Exception("User not found");
    }

hocuspocus|2 months ago

Not defending Go's braindead error handling, but you'll note that Swift is doubly coloring the function here (async throws).

tarentel|2 months ago

What is the problem with that though? I honestly wish they moved the async key word to the front `async func ...` but given the relative newness of all of this I've yet to see anyone get confused by this. The compiler also ensures everything is used correctly anyway.

saghm|2 months ago

And sometimes you even have to use the return value the way the function annoates as well instead of just pretending it's a string or whatever when it's not! Having the language tell me how to use things is so frustrating.