Live data from Hacker News

100 days with Rust: a series of brick walls

brandur.org

141–150 of 323 posts

Re: 100 days with Rust: a series of brick walls

#141
post #38

I am currently learning Rust and I feel this intensely[1]. I really want to like Rust, but I feel like the way they cope with no GC (lifetimes, borrowing) fights me at every turn, and really simple situations in other languages[2] become these intensely painful situations. Every time you think you've worked out how to fix a problem you find while you've fixed that one you've actually created 2 more. Want to have a da…

> I really want to like Rust, but I feel like the way they cope with no GC (lifetimes, borrowing) fights me at every turn, and really simple situations in other languages[2] become these intensely painful situations.

Lifetimes/borrowing aren't for dealing with no GC, they are for dealing with a number of issues (e.g., shared-state parallelism) that GC doesn't help at all with.

> In any case, my point remains the same: it is very challenging-- at least for someone used to just creating data structures and letting GC handle it-- to build code that does what you want, and you spend large amounts of time "fighting" the compiler.

In a sense, I think that's intentional with Rust. Not unnecessary difficulty, but Rust forces a lot of complexity that would otherwise be easy to overlook and cause runtime bugs to be dealt with upfront by the developer.

For the domain Rust aims at, that probably makes writing correct code easier on balance, but it does make lots of simpler cases harder and higher-overhead than they would be in Python, or even Java, or even in some cases C++, which also isn't GCed, but still leaves a lot of what Rust bakes into static compile-time checks as runtime footguns.

Re: 100 days with Rust: a series of brick walls

#142

Earlier quoted context omitted.

It's important to note that in C++ you can easily run into problems with what you're describing: structs (objects) with different sizes being put into a vector. You may very well get the program to compile, but then run into odd bugs which come about because your objects get clipped to the size of the smallest possible (the base class). So any overridden methods which expect extra data in a subclass will not behave a…

> It's important to note that in C++ you can easily run into problems with what you're describing: structs (objects) with different sizes being put into a vector. This is simply wrong. > You may very well get the program to compile, but then run into odd bugs which come about because your objects get clipped to the size of the smallest possible (the base class). That has nothing to do with object sizes. You're descri…

Okay, sure, if you want to get into the nitty gritty, that's fine. But I just wanted to provide a high level overview of how putting differently-sized structs into a vector can be problematic.

Sure, ignorance, whatever. Indeed. That's why I brought it up.

But my greater point still stands: do you want the compiler to catch these things? If so, you have trade-offs.

Re: 100 days with Rust: a series of brick walls

#143

Earlier quoted context omitted.

Automatic reference counting is just a slow form of GC.

Yes, slower than modern GC, but predictable and deterministic. Note that the parent comment never claimed it was faster, just that it avoids 'stop the world' which can be a problem in realtime contexts (e.g. games, audio).

Reference counting isn't inherently more predictable than tracing garbage collection. Take the example where your thread is the last one to deref a gigantic object graph. You're stuck holding the bag on traversing the entire graph and destructing it all at once, when a tracing gc could do it piecemeal (and on a dedicated background thread, instead of on your real worker threads).

Re: 100 days with Rust: a series of brick walls

#144
post #77

Earlier quoted context omitted.

Minor point: you can have Vectors in structs. Vector is a fixed-size type on the stack so it works fine. And other types in structs too. EDIT: sorry, I see you got to that before I'd finished this post :) Rust is a language where you have to kind of take a step back before working with it to read about the design tradeoffs and why they were made. Certain things you're used to doing with other languages just won't wor…

> lifetime system logical on its own terms but very, very unnatural to learn That is a succinct explanation of it, yes! Like I can read the documentation and nod along, and the rules seem simple. Actually then remapping your brain and how you want to achieve things seems incredibly challenging

Actually then remapping your brain and how you want to achieve things seems incredibly challenging

The thing that I have come to appreciate more strongly is that you actually have to think about the same ownership rules in other languages to write bug-free code. Consider e.g. the following Go code:

https://play.golang.org/p/3qfd5B8_ktK

Whether a subslice is still referring to the same array as the slice that it was derived from depends on whether there were any appends that required allocation of a larger backing array. Whether/when this happens is an implementation detail.

Rust prevents this ambiguity by not letting you borrow a `Vec` simultaneously as immutable and mutable (or multiple times mutably). Since the `push` method takes `&mut self`, you cannot have any mutable/immutable slices of the same data.

Another example. If you take a slice of a large array and only use that slice from there on, the slice will (obviously) prevent garbage collection of the backing array [1]. Again, Rust's ownership system prevents such memory leaks, since a slice reference cannot outlive its (owned) `Vec`.

[1] https://forum.golangbridge.org/t/free-memory-of-slice/3713/2

---

I would argue that the cognitive load in other languages is actually higher if you want to write correct code (except perhaps in languages with immutable data structures, such as Haskell), since you have now compiler helping you out. It's just that other languages allow us to cheat when it comes to ownership. For worse or better.

Re: 100 days with Rust: a series of brick walls

#145
I'm obviously about as far from the modal Rust user as one can get, but at this point the language has completely melted away into the background. I'm often tempted to write smallish scripts in dynamic languages, but even for those I frequently choose Rust just for the Cargo ecosystem. In particular I never see a reason to use C++ unless I'm contributing to a codebase that's written in it.

It takes different programmers different amounts of time to get to this point, and I certainly believe we can do better to make it easy to get up to speed, especially when async I/O is involved. But it does come eventually.

Re: 100 days with Rust: a series of brick walls

#146

Earlier quoted context omitted.

> No! You can rationalize any language problem away with this argument. Sorry, the sole problem is incompetence. Coupling incompetencr with the inability to learn is a problem that no programming language solves. If you insist in shooting yourself in the foot, it makes absolutely no sense to complain that the gun is broken because it doesn't guide bullets away from your foot when you intentionally aim at it. The obje…

"Don't use heap memory after it's been freed" is C++ (and C) 101 as well. Yet if the bar for C++ competence is "never wrote a use-after-free", then your bar for "competence" excludes essentially everyone in the industry. Whether a theoretically "competent" C++ developer makes mistakes related to object slicing is a completely uninteresting debate. Whether experienced C++ programmers make that mistake in practice is t…

> Yet if the bar for C++ competence is "never wrote a use-after-free"

The bar for C++ competence in that regard is RAII.

Furthermore, your strawman doesn't hold up. The object slicing problem is actually like someone being foolish enough to cast a 64-bit int to a 32-bit int and then complaining that the programming language is broken because the 32-bit variable doesn't hold 64 bits. Of course it doesn't. Why should it? And why make excuses an play the pin-the-blame game when the problem is caused by ignorance? Heck, you're assigning heterogenous objects of variable-length to a data structure that by definition was designed to hold homogeneous sequences of fixed-size objects, and even still you want to pin the blame on the language? How, if you clearly don't know what you're doing?

Re: 100 days with Rust: a series of brick walls

#147
post #126

Earlier quoted context omitted.

I can agree with the initial frustration coming from a GC-ed language until one figures out the right way to design code and structure data in a language with manual memory management. But I got over it eventually. I'm also curious about this nitty gritty bit. I see this: Generally, Self : Sized is used to indicate that the trait should not be used as a trait object. If the trait comes from your own crate, consider r…

Yeah. So I'm following a ray tracing book that uses C++ as example code, and am using Traits as a form of interface: there is a trait of Material that has a function on it, and then there are different "implementations" of Material with different implementations of that fn, and can have arbitrary data stored against them. I need to have them sized because I need to copy them at a point where I only understand they ar…

Would it work to add a boxed_clone() function to your Material trait that returns a Box? (or create a BoxedClone trait if you need that pattern more generally)

Implementations of boxed_clone() could still use &self.clone() to limit boilerplate.

Alternatively, if the Materials are not going to be modified and you just need multiple references to the same object, you could replace the Box with an Rc or Arc.

Re: 100 days with Rust: a series of brick walls

#148
> The wall is impassable until a point that you make a key discovery which lets you make your through. Often that discovery is a eureka moment – it felt impossible only hours before.

These moments are my favourite, or at least most memorable, parts of learning. Which is what attracts me to hard, important concepts because they tend to be the most rewarding once you begin to grasp it.

I'll never forget when SICP's connection between programming and abstraction started to really click, after much effort, and the way it blew my mind, despite having programmed for a couple years. Really grasping the fundamental concept took the whole experience of programming to the next level - and made learning other things easier like a rolling snowball.

The fact the author's walls never got "smaller" is a real problem, that can be disheartening.

That said, brick walls aren't always bad things. Although it's often hard to tell whether it's a reflection of a poor design/structure of the thing you're learning, or just the learning material, or the learner themselves. Then there is the question of "necessary evils" of steep learning curves which, who knows, might be required entry-fee for a truly great language or tool (see: Emacs/Vim).

Haskell was much the same way for me. Each cliff I climbed had a new big cliff waiting. It got me into learning not just pure FP but also lambda calculus/set/category theory. It felt never ending. I ultimately never went "all in" with Haskell (maybe because of this) but what I did learn has been very useful in my non-Haskell programming elsewhere, so it's not all for naught. But it was admittedly a hard and significant time investment which isn't for everyone... nor necessary for being a productive programmer. But I don't regret it.

Re: 100 days with Rust: a series of brick walls

#149

Earlier quoted context omitted.

Automatic reference counting is just a slow form of GC.

Yes, slower than modern GC, but predictable and deterministic. Note that the parent comment never claimed it was faster, just that it avoids 'stop the world' which can be a problem in realtime contexts (e.g. games, audio).

Reference counting can also lead to big cascades of objects being freed, which can make it impractical in realtime context as well. This makes RC useless for systems which have hard constraints here (e.g. automotive).

And of course, once you are multi-threaded, you can more or less forget about "predictable and deterministic" with RC, too.

In contrast, there are real time capable garbage collectors.

Re: 100 days with Rust: a series of brick walls

#150
post #100

Earlier quoted context omitted.

I can see where the author comes from. I've been working with ^W^W fighting against Tokio this week, and the error messages are horrible. Representative example: error[E0271]: type mismatch resolving ` + std::marker::Send>, [closure@src/server/mod.rs:59:18: 59:74]>, [closure@src/server/mod.rs:60:19: 69:10 next_connection_id:_], std::result::Result >, futures::MapErr , [closure@src/server/mod.rs:74:18: 74:74]>>, std::…

That's nothing! | 212 | / fn call(&self, payload: Self::Request) -> Self::Future { 213 | | let request = self.create_request(payload); 214 | | 215 | | let work = async_block! { ... | 254 | | FutureResponse(Box::new(work)) 255 | | } | |_____^ note: ...so that the type `impl futures::__rt::MyFuture request:hyper::Request for {futures::Async , (), fn(std::result::Result ) -> std::result::Result as std::ops::Try>::Ok, as…

I see. Rust is aiming to be closer and closer to C++ every day!

Anyways, I think it has still long way to go to match the length of even common C++ template related error messages...

Post reply on HN