7 ms·
A rarely covered topic in these discussions is architectural benefits that Go provides. Rust tends to solve problems by adding more constraints, which often le
by verdagon 4y ago
A rarely covered topic in these discussions is architectural benefits that Go provides.
Rust tends to solve problems by adding more constraints, which often leads to more complexity. It's great when that complexity is inherent (like with static typing), but quite painful when the complexity is artificial/incidental. In the domains I work in, the healthier approach is usually to decouple more, rather than add more constraints.
For example, in Rust, we often have to use an index or an ID where a regular reference would do in Go. We need to do extra refactoring of function signatures to pass collections down the call stack so we can then use that index/ID... and then find we can't modify a function signature because it's a trait override, and resort to tricky workarounds. In Go, we just use a reference. That choice is decoupled from memory concerns.
Another example is that in Rust, we often need to refactor to add `async` keywords to callers (and their callers) and run into the same problem. Go decouples all that coloring away.
Another example is how Go interfaces are structural, which further decouples a caller from a callee.
Hillel Wayne had a stellar article on how constraints lead to complexity: https://www.hillelwayne.com/post/complexity-constraints/ https://www.hillelwayne.com/post/complexity-constraints/
Go is designed to decouple away details that are unimportant for the vast majority of domains. That means a codebase can change faster, require less refactoring, and generally be healthier in an architectural sense.
That isn't saying that Rust is bad. It's amazing for embedded programming, it's much faster for some domains, and it doesn't have some of Go's other nonsense like nil and how their defer isn't block-scoped. But one can't discount Go's architectural benefits, especially when starting a new project that will be worked on by a large team.
Just my two cents, reasonable opinions may differ =)
- jayd16 4y agoI do wonder how this decoupling works for GUI dev where you need to explicitly juggle main thread and background work.
- gtowey 4y ago> especially when starting a new project that will be worked on by a large team. This alone is what makes me love go. It makes working with a team on shared code less of a hassle than any other language I've used. To me, that's worth more than nearly any amount of language features you could name.
- BreakfastB0b 4y ago> Go decouples all that coloring away. Go does have “coloured functions”, it’s called `context.Context`. Any function in go performing IO usually has to take `context.Context` as its first argument so it’s cancelable. Which then propagates that requirement to the caller just like `async`. That constraint can be discharged with `context.Background()` just like the async constraint can be discharged by not `await`ing the future in Rust.
- joshlemer 4y agoIME it's not uncommon for even a small a Java Spring application to take like a minute to build and like many seconds (30?) to properly start up and start taking requests.
- kaba0 4y agoHeh? Spring boot starts up in like 2-3 seconds. Of course you can register like 50 different messaging services or what not but then you are not comparing apples to oranges. Also, that seems like a very excessive build time, either there is some badly configured build script or you are again comparing a full fledged spring app with another vanilla framework.
- joshlemer 4y agoWe don't have a whole lot going on. I'm talking about building an uber jar with shadowJar gradle plugin. Maybe 20 endpoints on a single controller connecting, and connecting one Kafka publisher and Cassandra db.
- rcme 4y agoIf you don’t await, how do you get the result? Go returns the result even if you pass in context.Background(). Another subtle difference is that most functions take a context in Go. AFAIK, most functions in Rust are synchronous functions. Also, changing an immutable reference to a mutable one in can land you in a world of hurt in Rust.
- sidlls 4y agoGo’s structural interfaces are one of the worst features of the language, partially due to the implementation and partially due to the fact that structural typing like in Go is just a terrible idea in a statically typed language. May as well just use a dynamically typed language: because that’s what go’s duck typing leads a code base to be in practice anyway
- foota 4y agoDoesn't this also lead to a light implementation of monomorphization though?
- pcwalton 4y agoStructural vs. nominal typing has nothing to do with monomorphization. Depending on what you consider "structural typing" and how much you care about separate compilation, it may prevent you from doing monomorphization in some cases (e.g. OCaml's polymorphic variants require indirection at runtime). This doesn't apply to anything in Go or Rust, though.
- foota 4y agoYes... I guess this is the case. I was thinking that the GC shapes used in Go monomorphization were related to the structural typing, but thinking about it more, this isn't really the case.
- deleted 4y ago[deleted]
- hajile 4y agoStructural typing works very well in StandardML (and in Ocaml these days too). It's not structural typing, but how you use it. One of the biggest benefits of structural typing is how you can have most of the ergonomics of untyped languages while actually getting the performance and safety of sound typing.
- pkd 4y agoI disagree. The main difference in social dynamics of Go and Rust projects is that Rust forces you to model more of your domain upfront. This is a good thing, in my experience, even if it takes a little bit longer to get going because you end up with a more accurate picture of what your data looks like. Most importantly it provides you the tools to enforce the data model in ways that Go does not. It definitely lacks Go's many footguns too. Of course writing async code in Go is easier because of goroutines, but apart from that technically they are so different that it is a waste comparing the two.
- verdagon 4y agoForcing you into a model up-front can be good, but only if the model is a good one for the situation. It must have as many constraints as the situation calls for, and no more. Unfortunately, the borrow checker forces two extra constraints which often have little to do with the situation: - No shared mutability. This is an unrealistic constraint; our world calls for shared mutability all the time. Any time a Go reference becomes an index in Rust, you're experiencing this mismatch. - Single ownership (in the C++ and Rust sense). Most things can be phrased in terms of single ownership, but not all things should be, and it has a cost. Rust offers you tools to enforce those constraints, but those constraints are often artificial complexity and shouldn't have been added to a situation to begin with. Same with async/await. They solve a self-imposed problem that e.g. goroutines show us don't need to exist in the first place. We like these mechanisms because they make the program faster, but most of a program doesn't need to be fast. Only the hot path needs to be optimized, the rest of the program should be simpler, more flexible, more decoupled, and have less constraints. (This is one of the reasons some domains should use Rc and RefCell more and our community should stop vilifying them: they help provide flexibility to balance the borrow checker's constraints.) That said, this problem really only arises when one tries to use Rust in a domain it doesn't fit well. There are some domains which do fit the borrow checker's constraints really well, and they become benefits rather than sources of artificial complexity.
- josephcsible 4y ago> No shared mutability. This is an unrealistic constraint; our world calls for shared mutability all the time. Any time a Go reference becomes an index in Rust, you're experiencing this mismatch. Isn't the ease with which a reference can be replaced with an index evidence that you usually don't actually need shared mutability?