3 ms·
Yep. This was the biggest thing that turned me off Go. I ported the same little program (some text based operational transform code) to a bunch of languages - J
by josephg 10mo ago
Yep. This was the biggest thing that turned me off Go. I ported the same little program (some text based operational transform code) to a bunch of languages - JS (+ typescript), C, rust, Go, python, etc. Then compared the experience. How were they to use? How long did the programs end up being? How fast did they run?
I did C and typescript first. At the time, my C implementation ran about 20x faster than typescript. But the typescript code was only 2/3rds as many lines and much easier to code up. (JS & TS have gotten much faster since then thanks to improvements in V8).
Rust was the best of all worlds - the code was small, simple and easy to code up like typescript. And it ran just as fast as C. Go was the worst - it was annoying to program (due to a lack of enums). It was horribly verbose. And it still ran slower than rust and C at runtime.
I understand why Go exists. But I can't think of any reason I'd ever use it.
- andrewmcwatters 10mo agoThere's a lot of ecosystem behind it that makes sense for moving off of Node.js for specific workloads, but isn't as easily done in Rust. So it works for those types of employers and employees who need more performance than Node.js, but can't use C for practical reasons, or can't use Rust because specific libraries don't exist as readily supported by comparison.
- wswin 10mo agoRust gets harder with codebase size, because of borrow checker. Not to mention most of the communication libraries decided to be async only, which adds another layer of complexity.
- gpm 10mo agoI strongly disagree with this take. The borrow checker, and rust in general, keeps reasoning extremely local. It's one of the languages where I've found that difficulty grows the least with codebase size, not the most. The borrow checker does make some tasks more complex, without a doubt, because it makes it difficult to express something that might be natural in other languages (things including self referential data structures, for instance). But the extra complexity is generally well scoped to one small component that runs into a constraint, not to the project at large. You work around the constraint locally, and you end up with a public (to the component) API which is as well defined and as clean (and often better defined and cleaner because rust forces you to do so).
- josephg 10mo agoThis hasn't been my experience at all. I still regularly use typescript. One problem I run into from time to time is "spooky action at a distance". For example, its quite common to create some object and store references to it in multiple places. After all, the object won't be changed and its often more efficient this way. But later, a design change results in me casually mutating that object, forgetting that its being shared between multiple components. Oops! Now the other part of my code has become invalid in some way. Bugs like this are very annoying to track down. Its more or less impossible to make this mistake in rust because of how mutability is enforced. The mutability rules are sometimes annoying in the small, but in the large they tend to make your code much easier to reason about. C has multiple problems like this. I've worked in plenty of codebases which had obscure race conditions due to how we were using threading. Safe rust makes most of these bugs impossible to write in the first place. But the other thing I - and others - run into all the time in C is code that isn't clear about ownership and lifetimes. If your API gives me a reference to some object, how long is that pointer valid for? Even if I now own the object and I'm responsible for freeing it, its common in C for the object to contain pointers to some other data. So my pointer might be invalid if I hold onto it too long. How long is too long? Its almost never properly specified in the documentation. In C, hell is other people's code. Rust usually avoids all of these problems. If I call a function which returns an object of type T, I can safely assume the object lasts forever. It cannot be mutated by any other code (since its mine). And I'm not going to break anything else if I mutate the object myself. These are really nice properties to have when programming at scale.
- asa400 10mo agoI wholeheartedly concur based on my experience with Rust (and other languages) over the last ~7 or so years. > If I call a function which returns an object of type T, I can safely assume the object lasts forever. It cannot be mutated by any other code (since its mine). And I'm not going to break anything else if I mutate the object myself. These are really nice properties to have when programming at scale. I rarely see this mentioned in the way that you did, and I'll try to paraphrase it in my own way: Rust restricts what you can do as a programmer. One can say it is "less powerful" than C. In exchange for giving up some power, it gives you more information: who owns an object, what other callers can do with that object, the lifetime of that object in relation to other objects. And critically, in safe Rust, these are _guarantees_, which is the essence of real abstraction. In large and/or complicated codebases, this kind of information is critical in languages without garbage garbage collection, but even when I program in languages with garbage collection, I find myself wanting this information. Who is seeing this object? What do they know about this object, and when? What can they do with it? How is this ownership flowing through the system? Most languages have little/no language-level notion of these concepts. Most languages only enforce that types line up nominally (or implement some name-identified interface), or the visibility of identifiers (public/private, i.e. "information hiding" in OO parlance). I feel like Rust is one of the first languages on this path of providing real program dataflow information. I'm confident there will be future languages that will further explore providing the programmer with this kind of information, or at least making it possible to answer these kinds of questions easier.
- UltraSane 10mo agoasync seems sensible for anything subject to internet latency.
- yencabulator 10mo agoOnly in languages/runtimes without threads, like Javascript. In Rust, async vs threads is a performance tradeoffs (and it's definitely not always clear who the winner will be), and mostly relevant when you have tasks >> cores. Something like curl would have practically 0 reasons to be async, but of course is still subject to internet latency.
- jrjrjfhgggg 10mo agoDisagree, having dealt with +40k LoC rust projects, bottow checker is not an issue. Async is an irritation but not the end of the world ... You can write non asynchronous code I have done it ... Honestly I am coming around on async after years of not liking it... I wish we didn't have function colouring but yeah ... Here we are....
- jesse__ 10mo agoWe all know that lines of code is a poor measure of project size, but that said, 40k sloc is not a lot
- tracker1 10mo agoFunny, I explicitly waited to see async baked in before I even started experimenting with Rust. It's kind of critical to most things I work on. Beyond that, I've found that the async models in rust (along with tokio/axum, etc) have been pretty nice and clean in practice. Though most of my experience is with C# and JS/TS environments, the latter of which had about a decade of growing pains.
- raincole 10mo agoRust indeed gets harder with codebase size, just like other languages. But claiming it is because of borrow checker is laughable at best. Borrow checker is what keeps it reasonable because it limits the scope of how one memory allocation can affect the rest of your code. If anything, borrow checker makes writing functions harder but combining them easier.
- orlp 10mo agoI work in a 400k+ LOC codebase in Rust for my day job. Besides compile times being suboptimal, Rust makes working in a large codebase a breeze with good tooling and strong typechecking. I almost never even think about the borrow checker. If you have a long-lived shared reference you just Arc it. If it's a circular ownership structure like a graph you use a SlotMap. It by no means is any harder for this codebase than for small ones.
- tracker1 10mo agoI think it depends on the patterns in place and the actual complexity of the problems in practice. Most of my personal experience in Rust has been a few web services (really love Axum) and it hasn't been significantly worse than C# or JS/TS in my experience. That said, I'll often escape hatch with clone over dealing with (a)rc, just to keep my sanity. I can't say I'm the most eloquent with Rust as I don't have the 3 decades of experience I have with JS or nearly as much with C#. I will say, that for most of the Rust code that I've read, the vast majority of it has been easy enough to read and understand... more than most other languages/platforms. I've seen some truly horrendous C# and Java projects that don't come close to the simplicity of similar tasks in Rust.
- 9rx 10mo ago> it was annoying to program (due to a lack of enums) Typescript also lacks enums. Why wasn't it considered annoying? I mean, technically it does have an enum keyword that offers what most would consider to be enums, but that keyword behaves exactly the same as what Go offers, which you don't consider to be enums.
- hombre_fatal 10mo agoThey presumably mean tagged unions like `User = Guest | LoggedIn(id, username)`.
- 9rx 10mo agoThat wouldn't explain C, then, which does not have sum types either. All three languages do have enums (as it is normally defined), though. Go is only the odd one out by using a different keyword. As these programs were told to be written as carbon copies of each other, not to the idioms of each language, it is likely the author didn't take time to understand what features are available. No enum keyword was assumed to mean it doesn't exist at all, I guess.
- josephg 10mo agoC has numeric enums and tagged unions, which are sum types without any compile time safety. That’s idiomatic C. Go doesn’t have any equivalent. How do you do stuff like this in Go, at all? I’ve been programming for 30+ years. Long enough to know direct translations between languages are rarely beautiful. But I’m not an expert in Go. Maybe there’s some tricks I’m missing? Here’s the problem, if you want to have a stab at it. The code in question defines a text editing operation as a list of editing components: Insert, Delete and Skip. When applying an editing operation, we start at the start of the document. Skip moves the cursor forward by some specified length. Insert inserts at the current position and delete deletes some number of characters at the position. Eg: enum OpComponent { Skip(int), Insert(String), Delete(int), } type Op = List<OpComponent> Then there’s a whole bunch of functions with use operations - eg to apply them to a document, to compose them together and to do operational transform. How would you model this in Go?
- dent9 10mo ago> I understand why Go exists. But I can't think of any reason I'd ever use it. When you want your project to be able to cross-compile down to a static binary that the end user can simply download and run without any "installation" on any mainstream OS + CPU arch combination From my M1 Mac I can compile my project for Linux, MacOS, and Windows, for x86 and ARM for each. Then I can make a new Release on GitHub and attach the compiled binaries. Then I can curl the binaries down to my bare Linux x86 server and run them. And I can do all of this natively from the default Go SDK without installing any extra components or system configurations. You don't even need to have Go installed on the recipient server or client system. Don't even need a container system either to run your program anywhere. You cannot do this with any other language that you listed. Interpreted languages all require a runtime on the recipient system + library installation and management, and C and Rust lack the ability to do native out-of-the-box cross compilation for other OS + CPU arch combinations. Go has some method to implement enums. I never use enums in my projects so idk how the experience compares to other systems. But I'm not sure I would use that as the sole criteria to judge the language. And you can usually get performance on par with any other garage collected language out of it. When you actually care about the end user experience of running the program you wrote, you choose Go.