11 ms·
Notes on a Smaller Rust
- dwenzek 7y agoI like how the strengths of Rust are summarized by 3 points: * Algebraic data types * Resource acquisition is initialization * Aliasable XOR mutable I think it can help not only to design a Smaller Rust as discussed here, but also to grasp the key design points of this language which is a bit daunting.
- nudq 7y agoI like how these three points aren't just "strengths of Rust", but... > the necessary components ... to make imperative programming work as a paradigm You know, before Rust imperative programming just didn't work. Didn't work as a paradigm, even!
- _bxg1 7y agoIt had state mutation as a massive liability - which is probably why FP has seen such a surge in recent years. Rust is the only language that mostly solves that without totally changing the paradigm.
- 0815test 7y agoIt doesn't really change the paradigm compared to FP even, it just enforces a clear separation at all times between sequential state mutation on the one hand and references to shared (and generally immutable, except as provided for by explicit mutability mechanisms) state on the other. Which is essentially what FP languages end up doing, though they go about it somewhat differently.
- _bxg1 7y agoRight. The problem is that functional purity always comes with overhead; data structures like Clojure's help a whole lot, but there's still a cost. But one of the primary motivators for functional purity is to avoid unintended side-effects. In pure FP, You never even have to bother your mind with unexpected side-effects because there are no side-effects. Rust instead gives you the vocabulary to carefully articulate intended side-effects, preventing all other, unexpected side-effects at the same time. So you can colonize the wilderness, instead of avoiding it altogether, and for that you get to skip the overhead of treating everything as immutable. Not that this is an indisputable improvement for every use-case, but it's a novel tradeoff and one that is definitely preferable in many domains.
- skohan 7y agoThere seems to be a certain train of thought within the Rust community that unsafe languages are fundamentally invalid. I saw a comment on another forum claiming that C++ was even unsuitable for personal, experimental projects because unsafe code == undefinted behavior, and therefore your program produces "random results" which would not be fit for, say, scientific inquiry. Apparently the Linux kernel, and all code written before 2010 is just completely random!
- lmm 7y ago> Apparently the Linux kernel, and all code written before 2010 is just completely random! The Linux kernel is well known to be completely random when considered as a C program: building something useful out of it relies on a particular set of flags and ad-hoc implementation details of GCC (e.g. -fno-delete-null-pointer-checks).
- skohan 7y ago> building something useful out of it relies on a particular set of flags and ad-hoc implementation details of GCC (e.g. -fno-delete-null-pointer-checks). That makes it esoteric, not random. If the Linux kernel really did produce "random output" there is no way it would serve as the backbone of the global computing infrastructure, the financial system etc.
- lmm 7y ago> That makes it esoteric, not random. If the Linux kernel really did produce "random output" there is no way it would serve as the backbone of the global computing infrastructure, the financial system etc. To the extent that Linux is useful, it's not written in C. The particular binaries produced by GCC with particular flags have particular behaviour, more or less, but considered solely as a C program in terms of the C standard (i.e. behaviour on the C abstract machine), Linux does produce random output.
- tomp 7y agoFrom my observations, there seem to have been considerable efforts to avoid it being just random. Compiler & library "optimisations" have broken Linux before ("randomly") [0] [3]. Linus has ranted about this [1], multiple times [2] [3]. [0] https://lwn.net/SubscriberLink/793253/6ff74ecfb804c410/ https://lwn.net/SubscriberLink/793253/6ff74ecfb804c410/ [1] https://lkml.org/lkml/2018/6/5/769 https://lkml.org/lkml/2018/6/5/769 [2] https://bugzilla.redhat.com/show_bug.cgi?id=638477#c129 https://bugzilla.redhat.com/show_bug.cgi?id=638477#c129 [3] http://lkml.iu.edu//hypermail/linux/kernel/1407.3/00650.html http://lkml.iu.edu//hypermail/linux/kernel/1407.3/00650.html
- swsieber 7y ago> Rust works because it enables users to write in an imperative programming style, which is the mainstream style of programming that most users are familiar with, while avoiding to an impressive degree the kinds of bugs that imperative programming is notorious for. That's from the previous paragraph. So yeah, out of context it's a bad quote, but in the context it's a little less hoity toity.
- lasagnaphil 7y agoThere's also the trait system, which although isn't unique in Rust, has many advantages over the inheritance-based class system that languages such as C++/Nim/etc uses. Rust's trait system can be both used for static and dynamic polymorphism seamlessly (basically, impl T vs. dyn T). But in C++, you need different systems for doing so: templates (via hacks such as CRTP/SFINAE, or concepts in C++20) vs. virtual/override. Note that Go has interfaces which is the closest thing to traits, but only allows for dynamic polymorphism (because Go doesn't have generics yet... it seems like it's going down a similar path to C++20's concepts)
- rishav_sharan 7y agoAs someone who recognizes himself as beginner programmer, i do not consider the borrow checker as my major issue with the language. Heck, i haven't even reached that issue. Everytime that I have turned away from Rust, despite trying half a dozen times is due to its super busy and alien looking syntax. I just can't parse the code.
- paavohtl 7y agoWhat other languages do you have experience with? IMO the syntax is not wildly different from other statically typed curly bracket languages. IMO the most alien thing it has in terms of syntax is the use of a single quote for lifetime parameters ('a), but even that is based on the type parameter syntax for already existing languages (OCaml, F#, Standard ML).
- rishav_sharan 7y agoCrystal and vanilla es6 are my favoured languages. I also adore elixir. I have recently done some toy projects in f# using xelmish. I have written small projects in python, nodejs and lua. I have also tried Nim and wren. So while i am not much of a programmer, I love trying new languages. Out of all the languages that I tried, Rust and lisp are the two which scared me off outright just based on the syntax
- leshow 7y agoI don't see where you're coming from honestly. It has familiar C-style syntax, it has type parameters in the form java does with <>.
- rishav_sharan 7y agoSeeing my own list, i don't think i am comfortable with either c or java. So that's probably why. C, java, c++ are other notable languages i never could get into.
- 7y ago
- lmm 7y agoInteresting. Coming from an FP background I'd say the opposite - the confusing part of Rust is all these special-case imperative control flow keywords, a more smalltalk-like "everything is just values and functions" syntax would be the best way to simplify it. I'd agree with stepping away from guarantees about when allocations happen etc. But I think at the point where you remove those from Rust you really just have OCaml.
- cmrdporcupine 7y agoWell, it'd be OCaml without a garbage collector. And to be honest, that would be a very useful and handy language for systems development.
- masklinn 7y agoOCaml has for and while loops. Its for loop is even more imperative than Rust's (which simply drives an iterator, whereas OCaml's has "for a to b" and "for b downto a"). OCaml doesn't have break or continue though.
- lmm 7y agoOCaml is already a good language for systems development - people are far too scared of garbage collectors. And if you make the changes in the article then I think you give up most of the concrete benefits of non-GC - you won't have easy C interop without control of stack/heap/allocation, and since trait objects could end up nested arbitrarily far, I think you might still get GC-like pauses where a simple-looking code line ended up doing an arbitrary amount of unwrapping and freeing work.
- cmrdporcupine 7y agoI guess it depends on your definition of systems development. If you mean "writing systemsy applications" like IoT things or whatever, sure, fine, a GC isn't going to be an issue and probably will help. If you're talking about a language to write drivers or an operating system, etc in then a GC is just a big No. In that domain we don't even have Malloc/Free, let alone GC services. Having the overhead and normative lifestyle assumptions of _any_ kind of runtime is a big Nope.
- MrBuddyCasino 7y ago> I would probably experiment with exceptions if I were making this language. Hard disagree. Rust made conscious design decisions (i.e. limiting type inference) to enable local reasoning, and exceptions are non-local.
- barrkel 7y agoChecked exceptions (like Java) are isomorphic with Rust's Result type. You could convert a checked exceptions syntax into Result exception-carriers on each return with a mechanical transformation, of a Rust+exceptions program into Rust-today program.
- Sharlin 7y agoAnd nobody, nobody, likes checked exceptions. Perhaps with appropriate syntactic sugar they could be made ergonomic, but Java's checked exceptions at least were a failed experiment.
- tomp 7y agoJava's checked exceptions are a failed experiment because of subclassing (i.e. eventually you get `throws Throwable` which doesn't give you any useful information). More precise exception tracking is essentially isomorphic to current Rust (Result etc.).
- Quekid5 7y ago(Just adding a bit to that:) It's not just subclassing. It also breaks with higher-order functions, e.g. what is the throws clause of a map() method on lists? There's really no good answer in Java, so what people do in practice is to just re-wrap into a RuntimeException, but that leaves callers with a problem: Calling code cannot match on exception types using a catch clause on the checked exception -- it has to check for a RuntimeException with an embedded checked exception. This is disastrous for ergonomics and real-life use of checked exceptions. The Result/Either method leads to its own ergonomic problems, but I think they could be solvable with support for row-types and (anonymous) union types. At least in Haskell, the Either method leads to a proliferation of FooError types whose only purpose is to wrap other error types. Well, either that or you accept that every little thing can return in a generic 'AppError'.
- coldtea 7y ago>In other words, the core, commonly identified “hard part” of Rust - ownership and borrowing - is essentially applicable for any attempt to make checking the correctness of an imperative program tractable. So trying to get rid of it would be missing the real insight of Rust, and not building on the foundations Rust has laid out. Sure, but people wanting a smaller Rust might not care about the "real insight of Rust", but of the surface syntax, speed, apis, tooling (e.g. cargo), and so on... I'd like a Rust like language that's GCed for example, and drops all the lifetime annotations and so on...
- lucian1900 7y agoThat’s basically Swift.
- deleted 7y ago[deleted]
- mathw 7y agoI guess it depends what you think is the real core of Rust - for me, Rust's primary innovation and primary differentiation point is giving you safe memory management without a garbage collector. Anything that steps away from it (so stepping away from the borrow checker, lifetimes and so on) becomes something that isn't Rust anymore. After all, just about everything else in Rust is in Haskell.
- mrfredward 7y agoThe borrow checker is undoubtedly the core innovation of Rust, but it's not what people new to the language want. When I first came to rust, I was looking for a c++ alternative that had similar syntax and low level power, but also a sane standard library and modern language features like closures. When I first learned about the borrowchecker I thought it was brilliant. Then I spent some time fighting it and I hated it. Then I learned to love it...until I got backed in to a corner and had to re-architect a project I'd been working on for months. Now I have mixed feelings about it. So I agree Rust isn't Rust without the borrowchecker, but I empathize with all the noobs who want a rust without it because many people just want a language with some traction that fixes c++ past mistakes, and they end up getting a massive paradigm shift thrown at them instead.
- tomp 7y agoI'm really conflicted about > Aliasable XOR mutable because it prevents eventual synchronisation (e.g. single writer, multiple readers of a "monotonic" data structure like a counter)... I'm not convinced that it's entirely necessary, nor how easily it can be proved safe, but AFAIK many garbage collection runtimes work using eventual synchronisation... Probably other algorithms as well! Another thing that's not (AFAIK) covered by Rust's ownership system is flexible memory pools... where you can have proofs that some memory is accessible and pass those proofs around (whether at runtime, or just during compilation on the type system level)... although that might be entirely isomorphic to just passing around owned pointers to memory (pools), so maybe Rust does support it. People with lots of experience with Rust, what scenarios do you need to use unsafe Rust for?
- dbaupp 7y agoInterior mutability via atomic values (https://doc.rust-lang.org/std/sync/atomic/index.html https://doc.rust-lang.org/std/sync/atomic/index.html) works great for the first one, and, indeed, some sort of atomicity/synchronisation is required even in languages without the XOR rule (such as C++) to prevent undefined behaviour. There's a variety of safe arena types that give allocation inside pools too: https://crates.io/search?q=Arena&sort=downloads https://crates.io/search?q=Arena&sort=downloads
- tomp 7y agoAtomics are very useful indeed, but an overkill for some situations. In particular, if there's just one writer and multiple readers, the write doesn't need to be synchronised (as there's no possibility of someone else mutating the value in the middle of you incrementing it). An example would be the GC thread incrementing the epoch id/count that all mutator (user) threads read. Presumably you'd still need some sort of synchronisation otherwise the memory will stay mutated just in one CPU's cache, you need to "flush" the updated memory so that other CPUs can then see it, but that's likely cheaper than executing the whole operation atomically... Basically, increments are fundamentally not atomic (i.e. composed of multiple separate operations) whereas reads (of a single word) are, so if all mutations happen from a single thread (assuming a sensible CPU architecture and cooperative compiler) there's nothing else that needs to be done to ensure atomicity, you only need to care about the "happens before" relationship. But then I'm not a low-level programmer so the above is probably all wrong :)
- akavi 7y agoThe lack of ADTs in mainstream languages is really quite baffling, given their obvious utility. Thankfully it looks like they’re trickling in via Typescript/Swift/Kotlin.
- sambal 7y agoDo TS and Kotlin support them now? Last I checked TS was relying on string comparisons and Kotlin wasn’t even bothering outside sealed classes
- sixbrx 7y agoTS is a bit more powerful than it appears here, because string literal values are recognized at the type level, so can be checked by the compiler when distinguishing cases. See for example: "Discriminated Unions" at https://www.typescriptlang.org/docs/handbook/advanced-types.html#discriminated-unions https://www.typescriptlang.org/docs/handbook/advanced-types.....
- skohan 7y ago> Resource acquisition is initialization I recently built a small prototype Entity Component System, and one of the big learnings for me was to what extent RAII is an anti-pattern with respect to performance. It's actually amazing how fast a modern CPU can be when you're not using it to allocate and initialize small blocks of memory at a time. I understand the benefits of RAII in terms of bookkeeping, but it seems like there is opportunity for a new resource management paradigm which is more optimized for using computing resources.
- PyroLagus 7y agoCan't you just initialize everything at once before you need it rather than initializing one object here and there?
- skohan 7y agoIt depends heavily on the problem domain. In some cases this is possible, but it's often the case that you will not know what resources you will need at the beginning of execution. Also the implementation of "initialize everything at once" in an RIAA paradigm is not necessarily going to be the most efficient possible implementation. For instance, I might want to do something like this (in a made-up language): struct A { ... } func init() { let a1 = new A(); let a2 = new A(); let a3 = new A(); } The most efficient way to implement this would be to allocate enough memory to store a1, a2, and a3 and then run the initialization method 3 times over that memory. But it's likely that instead the compiler will allocate a1, initialize a1 and so on with a2 and a3. Since the allocations are separate, maybe another thread takes precedence between the allocation of a2 and a3 and allocates some memory, so these values are not contiguous in memory, and I get worse caching performance when accessing them. In this toy example with 3 values it doesn't make aa lot of difference, but if you add up all these marginal costs in a large application it absolutely does cost a lot of performance.
- 0815test 7y ago> The most efficient way to implement this would be to allocate enough memory to store a1, a2, and a3 and then run the initialization method 3 times over that memory. Only if a1, a2, a3 are also freed at once. In which case, you can just put all three in a struct that is allocated as a single block. Rust doesn't yet support this very well in the general case, because its support for what C++ calls "placement new" and customized allocation in general is not complete or stabilized. But doing this in a more hackish special-cased way is already possible in many cases.
- nixpulvis 7y ago> As I said once, pure functional programming is an ingenious trick to show you can code without mutation, but Rust is an even cleverer trick to show you can just have mutation. Yea, I like this.
- Aardappel 7y agoSee, everyone has a different thing they find important about Rust. He suggest abandoning zero cost abstractions (letting stack/inline allocation be decided by the compiler instead) which I think would remove most of the power of the language. Default thread safe primitives sounds really wasteful too. I think languages similar to Rust but with less complexity will be a very cool space to watch going forward. All sorts of interesting language designs possible. I’m working on a language that does “Rust-like” things, but unlike the article, what I am going for is the efficient memory management, in this case the inline structs (zero cost abstraction) and compile time memory management (automatic lifetime analysis). The biggest difference with Rust is that it is fully automatic: when ownership can’t be determined at compile time, by defaults it falls back to a reference count increase at runtime (in Rust this would just be an error). The advantage is zero annotations, and a language that can be used mostly by people that don’t even understand ownership. There may be ways to explicitly (optionally) enforce ownership in the future, for those who want more control. Language: http://strlen.com/lobster/ http://strlen.com/lobster/ Details on memory management: http://aardappel.github.io/lobster/memory_management.html http://aardappel.github.io/lobster/memory_management.html