Live data from Hacker News

Flattening Rust’s learning curve

corrode.dev

361–370 of 405 posts

Re: Flattening Rust’s learning curve

#361

Earlier quoted context omitted.

It does sound like quite a similar model; unsafe Rust in self contained regions, safe in the majority of areas. FWIW in the case where you're not separating code via a dynamic library boundary, you give the compiler an opportunity to optimise across those unsafe usages, e.g. inlining opportunities for the unsafe code into callers.

> quite a similar model Yeah, and that model is rather old: https://en.wikipedia.org/wiki/Greenspun%27s_tenth_rule In practice, complex software systems have been written in multiple languages for decades. The requirements of performance-critical low-level components and high-level logic are too different and they are in conflict. > you give the compiler an opportunity to optimise across those unsafe usages One worka…

> injecting C# dependencies via function pointers or an abstract interface

This is the opposite of what I was suggesting though; those function pointers or abstract interfaces inhibit the kind of optimisations I was suggesting (e.g. inlining causing dead code removal of bounds checks, or inlining comparison functions into sort implementations, classics).

EDIT: that said, it's definitely still possible to not let it impact performance, it just takes being somewhat careful when making the interface, which you don't have to think about if it's all the same compiler/link step

Re: Flattening Rust’s learning curve

#362
post #18

It's like reading "A Discipline of Programming", by Dijkstra. That morality play approach was needed back then, because nobody knew how to think about this stuff. Most explanations of ownership in Rust are far too wordy. See [1]. The core concepts are mostly there, but hidden under all the examples. - Each data object in Rust has exactly one owner. - Ownership can be transferred in ways that preserve the one-owner ru…

Maybe it's my learning limitations, but I find it hard to follow explanations like these. I had similar feelings about encapsulation explanations: it would say I can hide information without going into much detail. Why, from whom? How is it hiding if I can _see it on my screen_. Similarly here, I can't understand for example _who_ is the owner. Is it a stack frame? Why would a stack frame want to move ownership to it…

> Could owner be something else than a stack frame?

Yes. There are lots of ways an object might be owned:

- a local variable on the stack

- a field of a struct or a tuple (which might itself be owned on the stack, or nested in yet another struct, or one of the other options below)

- a heap-allocating container, most commonly basic data structures like Vec or HashMap, but also including things like Box (std::unique_ptr in C++), Arc (std::shared_ptr), and channels

- a static variable -- note that in Rust these are always const-initialized and never destroyed

I'm sure there are others I'm not thinking of.

> Why would a stack frame want to move ownership to its callee, when by the nature of LIFO the callee stack will always be destroyed first

Here are some example situations where you'd "pass by value" in Rust:

- You might be dealing with "Copy" types like integers and bools, where (just like in C or C++ or Go) values are easier to work with in a lot of common cases.

- You might be inserting something into a container that will own it. Maybe the callee gets a reference to that longer-lived container in one of its other arguments, or maybe the callee is a method on a struct type that includes a container.

- You might pass ownership to another thread. For example, the main() loop in my program could listen on a socket, and for each of the connections it gets, it might spawn a worker thread to own the connection and handle it. (Using async and "tasks" is pretty much the same from an ownership perspective.)

- You might be dealing with a type that uses ownership to represent something besides just memory. For example, owning a MutexGuard gives you the ability to unlock the Mutex by dropping the guard. Passing a MutexGuard by value tells the callee "I have taken this lock, but now you're responsible for releasing it." Sometimes people also use non-Copy enums to represent fancy state machines that you have to pass around by value, to guarantee whatever property they care about about the state transitions.

Re: Flattening Rust’s learning curve

#363

[flagged]

Rust design decisions are pretty hard to understand sometimes, Mojo is another language with a borrow-checker but it is not nearly as hard to learn as Rust due to making a few decisions. First is value semantics, in Rust people are told to always clone when learning, why isn't this semantics built into the language? It is what you have in most static languages - C, C++, Go, etc. This is the mental model many people c…

> but values will be destroyed immediately after their last use

For what it's worth, it appears this was considered for Rust at some point but the devs decided against it. As described by Steve Klabnik in 2018 [0]:

> This was called “early drop”, and we didn’t implement it because of worries about unsafe code. Yes, the compiler could tell for safe code, and it would be fine, but unsafe code cannot, by definition, be checked.

[0]: https://users.rust-lang.org/t/drop-values-as-soon-as-possibl...

Re: Flattening Rust’s learning curve

#364

Earlier quoted context omitted.

> There is almost no case where you need to traverse a list in both directions But you might need to remove a given element that you have a pointer to in O(1), which a singly linked list will not do

Getting the pointer to that element means randomly hopping around the heap to traverse the list though. Linked lists are perfect for inserting/deleting nodes, as long as you never need to traverse the list or access any specific node.

You’re assuming no other data structure points to the element. It may. Example: implement a cache.

Each element is: key, value, linked list node for hash table bucket, linked list node for LRU. Hash table to look up element. Element is both a member of hash table and of linked list. Linked list is used as LRU for feeling memory when needed.

LRU never traversed but often needs removal and reinsertion.

Re: Flattening Rust’s learning curve

#365

Earlier quoted context omitted.

> I've been using it in C# One reason why async-await is trivial in .NET is garbage collector. C# rewrites async functions into a state machine, typically heap allocated. Garbage collector automagically manages lifetimes of method arguments and local variables. When awaiting async functions from other async functions, the runtime does that for multiple async frames at once but it’s fine with that, just a normal objec…

None of this is scary. > The concurrency runtime is implemented by “Tokio” external library. Scare quotes around Tokio? You can't use Rails without Rails or Django without Django. The reason Rust keeps this externally is because they didn't want to bake premature decisions into the language. Like PHP's eternally backwards string library functions or Python's bloated "batteries included" standard library chock full of…

> I can write async Rust without breaking a sweat

The difficult part comes when you try to do so "correctly".

Re: Flattening Rust’s learning curve

#366

Earlier quoted context omitted.

I think your comment has received excellent replies. However, no one has tackled your actual question so far: > _who_ is the owner. Is it a stack frame? I don’t think that it’s helpful to call a stack frame the owner in the sense of the borrow checker. If the owner was the stack frame, then why would it have to borrow objects to itself? The fact that the following code doesn’t compile seems to support that: fn main()…

I believe this answer is correct. Ownership exists at the language level, not the machine level. Thinking of a part of the stack or a piece of memory as owning something isn’t correct. A language entity, like a variable, is what owns another object in rust. When that object goes at a scope, its resources are released, including all the things it owns.

I think it's funny how I had this kind of sort of "clear" understanding of Rust ownership from experience, and asking "why" repeatedly puts a few holes in the illusion of my understanding being clear. It's mostly familiarity of concepts from working with C++ and RAII and solving some ownership issues. It's kind of like when people ask you for the definition of a word, and you know what it means, but you also can't quite explain it.

I would say you're correct that ownership is something that only exists on the language level. Going back to the documentation: https://doc.rust-lang.org/book/ch04-01-what-is-ownership.htm...

The first part that gives a hint is this

>Rust uses a third approach: memory is managed through a system of ownership with a set of rules that the compiler checks.

This clearly means ownership is a concept in the Rust language. Defined by a set of rules checked by the compiler.

Later:

>First, let’s take a look at the ownership rules. Keep these rules in mind as we work through the examples that illustrate them:

>

>*Each value in Rust has an owner*.

>There can only be one owner at a time.

>*When the owner goes out of scope*, the value will be dropped.

So the owner can go out of scope and that leads to the value being dropped. At the same time each value has an owner.

So from this we gather. An owner can go out of scope, so an owner would be something that lives within a scope. A variable declaration perhaps? Further on in the text this seems to be confirmed. A variable can be an owner.

>Rust takes a different path: the memory is automatically returned once the variable that owns it goes out of scope.

Ok, so variables can own values. And borrowed variables (references) are owned by the variables they borrow from, this much seems clear. We can recurse all the way down. What about up? Who owns the variables? I'm guessing the program or the scope, which in turn is owned by the program.

So I think variables own values directly, references are owned by the variables they borrow from. All variables are owned by the program and live as long as they're in scope (again something that only exists at program level).

Re: Flattening Rust’s learning curve

#367

Earlier quoted context omitted.

The truth is that by the time you are a senior developer, you will have encountered the lessons that make rust worth learning but may not have truly understood all the implications. Many people will think, I have a garbage collected language, rust has nothing to teach me. Even in garbage collected languages, people create immutable types because the possibility of shared references with mutability makes things incred…

> Not understanding the lifetimes of objects is what makes shared mutability hard. Well, no; in my experience the difficulty overwhelmingly comes from thinking about the semantics. I.e.: these two clients currently share a mutable object; should they observe each others' mutations? Or: if I clone this object, will I regret not propagating the change to other clients?

If you understand how it will work then that is just a decision to be made and a decision (although design is not necessarily easy) isn't what I would call hard: it is the ordinary work. By difficulty I mean bugs and bugginess, and bugs happen in this area because there are unintentional race conditions on the mutability. Total immutability is merely one way to force yourself to understand it, if those two clients need to observe the modifications then you have to propagate the changes manually instead of relying on shared memory semantics. But worse than that, even if you decide that they should observe the changes, then you can often end up with tearing if you are changing multiple properties non-atomically. That is a lifetime issue: the mutable reference is allowed to coexist at the same time as an immutable reference. In rust you cannot share an ordinary reference to have runtime observability like that, once shared the object becomes immutable. This forces you to use internal mutability via a RefCell and the exclusive/shared reference is enforced at runtime to eliminate tearing. Lifetimes of these borrows matter and how they matter depends on the choice of semantics, but I wouldn't call the choice the hard part.

Re: Flattening Rust’s learning curve

#368

[flagged]

This comment set off a generic programming language debate, which is the just the kind of geek message board cliché we're hoping to avoid on HN!

Eschew flamebait. Avoid generic tangents. Omit internet tropes.

https://news.ycombinator.com/newsguidelines.html

Re: Flattening Rust’s learning curve

#369
post #192
post #183

Earlier quoted context omitted.

"raw pointers are one of the most important concepts in CS" that's a reach and a half, I don't remember the last time I've used one

The concept of being able to reference a raw memory address and then access the data at that location directly feels pretty basic computer science. Perhaps you do software engineering in a given language/framework? A clutch is fundamental to automotive engineering even if you don’t use one daily.

I thought you meant untyped ones. Afaik most algorithms and data structures use typed pointers. Ofc low level computer engineering stuff uses untyped pointers. That indeed touches the point of the neighbour comment, to me this is computer engineering, not computer science, but reasonable minds may disagree

Re: Flattening Rust’s learning curve

#370
post #329

Earlier quoted context omitted.

>Your programming language shouldn't constrict you in those ways Says who? Programming languages come in all shapes and sizes, and each has their tradeoffs. Rust's tradeoff is that the compiler is very opinionated about what constitutes a valid program. But in turn it provides comparable performance to C/C++ without many of the same bugs/security vulnerabilities.

Also: everybody can write a program that does the thing it is intended to do. That is the easy part. The hard part is writing a program that does not do things it isn't intended to do while existing in a ever changing environment and even be subjected to changes of its own source code. So the hard part isn't getting code to work, it is ensuring it is only working in the intended ways, even when your co-worker (or you…

Restrictions foster creativity, and also free up mindspace to think about your actual problem in more detail.
Post reply on HN