I always thought of Go as low level and Rust as high level. Go has a lot of verbosity as a "better C" with GC. Rust has low level control but many functional inspired abstractions. Just try writing iteration or error handling in either one to see.
I always thought of Go as low level and Rust as high level. Go has a lot of verbosity as a "better C" with GC. Rust has low level control but many functional inspired abstractions. Just try writing iteration or error handling in either one to see.
I wonder if it's useful to think of this as go is low type-system-complexity and rust is high type-system-complexity. Where type system complexity entails a tradeoff between the complexity of the language and how powerful the language is in allowing you to define abstractions.
As an independent axis from close to the underlying machine/far away from the underlying machine (whether virtual like wasm or real like a systemv x86_64 abi), which describes how closely the language lets you interact with the environment it runs in/how much it abstracts that environment away in order to provide abstractions.
Rust lives in high type system complexity and close to the underlying machine environment. Go is low type system complexity and (relative to rust) far from the underlying machine.
I think this is insightful! I'm going to ponder it, thank you. I think it may gesture towards what I'm trying to get at.
Rue author here, yeah I'm not the hugest fan of "low level vs high level" framing myself, because there are multiple valid ways of interpreting it. As you yourself demonstrate!
As some of the larger design decisions come into place, I'll find a better way of describing it. Mostly, I am not really trying to compete with C/C++/Rust on speed, but I'm not going to add a GC either. So I'm somewhere in there.
How very so humble of you to not mention being one of the primary authors behind TRPL book. Steve you're a gem to the world of computing. Always considered you the J. Kenji of the Rust world. Seems like a great project let's see where it goes!
That is a very kind thing to say, I admire him quite a bit. Thank you!
> Mostly, I am not really trying to compete with C/C++/Rust on speed, but I'm not going to add a GC either. So I'm somewhere in there.
Out of curiosity, how would you compare the goals of Rue with something like D[0] or one of the ML-based languages such as OCaml[1]?
EDIT:
This is a genuine language design question regarding an imperative/OOP or declarative/FP focus and is relevant to understanding the memory management philosophy expressed[2]:
0 - https://dlang.org/1 - https://ocaml.org/
2 - https://rue-lang.dev/
Closer to an OCaml than a D, in terms of what I see as an influence. But it's likely to be more imperative/FP than OOP/declarative, even though I know those axes are usually considered to be the way you put them than the way I put them.
Do you think you'll explore some of the same problem spaces as Rust? Lifetimes and async are both big pain points of Rust for me, so it'd be interesting to see a fresh approach to these problems.
I couldn't see how long-running memory is handled, is it handled similar to Rust?
I'm going to try and avoid lifetimes entirely. They're great in Rust! But I'm going to a higher level spot.
I'm totally unsure about async.
Right now there's no heap memory at all. I'll get there :) Sorta similar to Rust/Swift/Hylo... we'll see!
So if you don't have a garbage collector, and you don't have manual memory management, and you don't have lifetimes... What do you have?
The plan is something like mutable value semantics and linear types. I'm figuring it out :)
Since it's framed as 'in between' Rust and Go, is it trying to target an intersection of both languages' use-cases?
I don't think you'd want to write an operating system in Rue. I may not include an "unsafe" concept, and will probably require a runtime. So that's some areas where Rust will make more sense.
As for Go... I dunno. Go has a strong vision around concurrency, and I just don't have one yet. We'll see.
Is this a simplified / distilled version of Rust ? Or Subset of Rust with some changes ?
Some of it is like that, but some of it is going to be from other stuff too. I'm figuring it out :)
Wow didn't realise it was you who was the author. I learnt a lot about Rust from your writings.
I'm glad to have helped you :)
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.
Rust 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.
I 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).
Disagree, 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....
We all know that lines of code is a poor measure of project size, but that said, 40k sloc is not a lot
This 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.
I 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.
> 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
Your paraphrasing reminds me a bit of structured vs. unstructured programming (i.e., unrestricted goto). Like to what you said, structured programming is "less powerful" than unrestricted goto, but in return, it's much easier to follow and reason about a program's control flow.
At the risk of simplifying things too much, I think some other things you said make for an interesting way to sum this up - Rust does for "ownership flow"/"dataflow" what structured programming did for control flow.
async seems sensible for anything subject to internet latency.
[dead]
C was designed as a high level language and stayed so for decades
> C was designed as a high level language and stayed so for decades
C was designed as a "high level language" relative to the assembly languages available at the time and effectively became a portable version of same in short order. This is quite different to other "high level languages" at the time, such as FORTRAN, COBOL, LISP, etc.