10 ms·
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) figh
by SCdF 9y ago
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 data structure of variable size (eg a struct with an Vector in it)? You can't do that, structs have to be fixed size. OK so I'll make it a reference to a Vector. That's great, but now you can't have a factory function because the lifetime goes out of scope. OK so I'll wrap my reference in a Box. OK that's great but now your OTHER reference: a trait (because traits are also of unknown size) is complaining. OK I'll wrap that in a Box as well. Sorry, you can't wrap this trait in a box because before your trait requires implementors to implement copy because at one point you have to use the trait in more than one place and references break the lifetimes and--- THROWS LAPTOP OUT WINDOW
I'm going to keep chipping away at it for a bit longer, but I can feel my interest waning.
[1] Except for the bad docs bit, I really like the docs, and the error messages are definitely earnest in their attempts to help you.
[2] that I'm familiar with, which are no languages that don't auto-GC
-- EDIT --
To everyone taking the first line of my intentional rant paragraph and pointing out it works fine… you're correct, I am mistaken! Let me paste the reply I made to the first person:
---
You're absolutely right. What I really meant was that you can't have Vec<T> where T is a trait without also wrapping that in a box, which for me in turn doesn't work for a bunch of other reasons.
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. I am OK if people wish to peg that on me being stupid or whatever, it doesn't change the core point: it's hard for new people to get into, and if you're wanting there to be less Electron apps and more native apps things like Rust being easy to use seems important for that.
- physguy1123 9y agoThere's nothing at all preventing you from having a Vec in a struct, and the use cases for variable sized structs are pretty rare. It sounds more like you conflated having a different problem with having a Vec be in a struct and stuff like that poisoned your further attempts to understand the language
- SCdF 9y agoYou're absolutely right. What I really meant was that you can't have Vec<T> where T is a trait without also wrapping that in a box, which for me in turn doesn't work for a bunch of other reasons. 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. I am OK if people wish to peg that on me being stupid or whatever, it doesn't change the core point: it's hard for new people to get into, and if you're wanting there to be less Electron apps and more native apps things like Rust being easy to use seems important for that.
- comex 9y agoIt fundamentally doesn't make sense to have a Vec<Trait>, because Vec expects each of its elements to be the same size (otherwise you couldn't index into it in O(1)), but different types that implement Trait can have different sizes. Other languages allow this because they automatically box everything, but Vec<Box<Trait>> should accomplish the same in Rust; I'm curious what the "other reasons" you referred to are, that make that unsuitable. Alternately, if any given instance of the Vec is only expected to have values of a single type (but the type can be different for different Vecs), you may be able to accomplish what you want with generics instead. That said, trait objects in Rust are pretty broken in general, so - this is just speculation, but - I think your problem might actually arise from that, and the Vec<Trait> issue is a red herring.
- SCdF 9y agoI didn't really want to get into the nitty gritty because it detracts from the point (I considered not having the rant paragraph at all for fear that it would just be deconstructed and the exact examples becoming the focus of what I said and not what I was actually trying to say). But anyway!: https://doc.rust-lang.org/error-index.html#E0038 https://doc.rust-lang.org/error-index.html#E0038 <-- these are restrictions on Box<Trait>. The one that screws me is the first one, requiring Sized.
- foota 9y agoI feel you. To nit though, I think the right thing to do regarding the vector is the tie the vector's lifetime to the struct. (I don't know how exactly to do that syntactically)
- kibwen 9y ago> Want to have a data structure of variable size (eg a struct with an Vector in it)? You can't do that, structs have to be fixed size. I'm unclear what you mean here, because Vecs in Rust do have a fixed size. You can see this by using std::mem::size_of on a Vec: for any type, a Vec is three words in size. You can see this documented in the stdlib documentation for Vecs: https://doc.rust-lang.org/std/vec/struct.Vec.html#guarantees https://doc.rust-lang.org/std/vec/struct.Vec.html#guarantees "Vec is and always will be a (pointer, capacity, length) triplet. No more, no less." So what you're asking for, a Vec in a struct, works just fine: struct Foo { v: Vec<i32> } let foo = Foo { v: vec![1,2,3] }; Can you elaborate on what trouble you're having?
- Jweb_Guru 9y agoYou can also make a struct actually variable size anyway...
- kibwen 9y agoYou can, but depending on what you actually want to do with that struct it can be pretty grotty to instantiate. Very much not beginner territory. I believe there's currently an approved RFC for making it easier to work with dynamically-sized data.
- kibwen 9y ago(Responding to myself to reply to the parent's edit) > You're absolutely right. What I really meant was that you can't have Vec<T> where T is a trait without also wrapping that in a box, which for me in turn doesn't work for a bunch of other reasons. Ah, it sounds like you're using traits as types directly, which is very much discouraged by Rust (especially in conjunction with taking references to those traits). What the language really prefers for you to do is to use traits as bounds on generic types to get rid of the dynamic dispatch and the consequent complications with lifetimes. The only time I'd suggest using traits in the manner you've described is when you need a heterogenous collection, which isn't common in my experience. You've just so happened to stumble across one of the patterns that I most suggest beginners not to do. :P > 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. I am OK if people wish to peg that on me being stupid or whatever, it doesn't change the core point: it's hard for new people to get into, and if you're wanting there to be less Electron apps and more native apps things like Rust being easy to use seems important for that. I hope that nobody here's making you feel stupid, that would be pretty silly. I've been helping people learn Rust for a long time and it's indeed common for people coming from GC'd/dynamic languages to feel like things are pretty alien (to some degree attributable simply to the differences inherent to systems programming). I as well came from Java/Python and found that there were enough people in the Rust community with that same background that there's no air of elitism suggesting that one ought to feel like a moron for e.g. not knowing what a pointer is. We're all here to help each other, eh? If you ever want to give learning Rust a try again and get stuck, feel free to come ask questions on #rust at irc.mozilla.org or reddit.com/r/rust.
- scrumper 9y agoMinor 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 work (e.g. doubly linked lists), and trying to coerce the language into being something it's not leads to you going on one of those frustrating circular rabbit hole journeys you described so nicely above, usually ending up with laptop defenestration. I'm a week into Rust. I find like you the compiler quite helpful, the docs comprehensive but a bit impenetrable, and the ownership & lifetime system logical on its own terms but very, very unnatural to learn. I don't actually know if its worth it yet. I am really, really enjoying the O'Reilly Rust book ("Programming Rust"). Maybe sign up for a 10 day Safari trial (free, no card) and give the first few chapters a read. It may help reset you.
- SCdF 9y ago> 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
- scrumper 9y agoWell, that's down to practice. And on that subject... One thing I like to do with a new language is to re-implement a self-contained but non-trivial program that I've built before. I have a few but my favorite is a genetic algorithm which breeds rule sets for a cellular automaton to get the CA to solve some simple computation problems. It's not at all complex, a few hundred lines when done in Python, but it's more than a tiny example. I like it for learning new languages as it touches on various data structures, it's inherently parallelizable, it's not especially bound to any particular programming paradigm, it lends itself to exploring various useful concepts and constructs (function pointers, closures, recursion, etc.), and it is fun and interesting to work on. So maybe you have something similar, something where you really understand the problem and the solution, freeing your brain to focus entirely on seeing how it would map to Rust.
- gnode 9y agoAn example of a variable size Vec in a struct: My understanding is that technically the Vec object itself is fixed size under the hood (a pointer and size field), but as far as I can tell, this is what you're after. It needs no references or boxes. https://play.rust-lang.org/?gist=8d88b83daa2f389892bfa95c8dbd9423&version=stable https://play.rust-lang.org/?gist=8d88b83daa2f389892bfa95c8db... extern crate rand; use rand::Rng; #[derive(Debug)] struct MyStruct { v: Vec<u32> } impl MyStruct { pub fn new() -> MyStruct { let mut rng = rand::thread_rng(); let n = rng.gen_range( 4, 10 ); MyStruct { v: rng.gen_iter().take( n ).collect() } } } fn main() { let ms = MyStruct::new(); println!( "{:?}", ms ); }
- skohan 9y agoTwo things jump out at me: 1. Automatic reference counting is a really good alternative to GC. It takes a little bit more book-keeping, but the performance characteristics are predictable since allocations/frees are handled along the way. Many GC implementations require execution to be halted while the reference graph is traced, which makes it a non-starter for applications trying to deliver predictable real-time performance. 2. Most of the limitations in Rust you're lamenting are there to ensure that your code is safe and performant. Rust requires adapting to very different design-patterns than you might be used to to get these benefits: if something is hard to do in Rust it's probably an anti-pattern with respect to memory performance or safety. Then again if performance isn't your main concern, maybe you don't need Rust.
- Koshkin 9y ago> Automatic reference counting is a really good alternative to GC 1) GC is lazy, whereas RC is eager; 2) the use of "pure" RC may lead to memory leaks.
- vlovich123 9y agoMemory leaks occur in GC too.
- pcwalton 9y agoAutomatic reference counting is just a slow form of GC.
- widdershins 9y agoYes, 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).
- jmgao 9y agoReference 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).
- tkzzbneig 9y agoIt'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 as expected. So what you usually do here is have a pointer and a VTable and all that jazz. But there's been a resurgence in interest in putting data into contiguous blocks of memory. Check up on data driven design. I don't think it's fair to compare GC'd languages to Rust's complexity. At least, compare C to Rust. But still, to be truly fair, weigh the usability differences against the safety differences. There are a lot of trade-offs here and perhaps this isn't the right way for you to go forward, but keep a broad view of the other aspects at play.
- jcelerier 9y ago> So what you usually do here is have a pointer and a VTable and all that jazz. But there's been a resurgence in interest in putting data into contiguous blocks of memory. Check up on data driven design. in particular, boost.polycollection is a nice implementation of heterogeneous vectors: http://www.boost.org/doc/libs/develop/doc/html/poly_collection.html http://www.boost.org/doc/libs/develop/doc/html/poly_collecti...
- alexeiz 9y ago> 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. Baloney. In C++ vector<any> or vector<variant> accomplish this task without any problems whatsoever.
- pcwalton 9y agoAnd you can do the same in Rust with Box<Any>. What Rust doesn't have is the object slicing gotcha, which is probably a language design mistake in C++.
- 9y ago
- oconnor663 9y agoResponding to your updated question: > What I really meant was that you can't have Vec<T> where T is a trait without also wrapping that in a box, which for me in turn doesn't work for a bunch of other reasons. You can use any type of pointer to refer to a trait object, not just Box. In particular, you can use &T where T is a trait. The vtable work happening at runtime is the same, but the object can live e.g. on the stack or in a vec somewhere. Of course, if you're using references, you have to convince the compiler that the object is going to stay put for as long as the reference exists, as usual for Rust. When that's not practical, usually a Box or Rc/Arc is the go-to solution. Could you tell me more about what makes Box not work for your use case? Aside: Trait objects are one of the more complicated features of Rust, and they run into tricky limitations (like "object safety"). It's often people's first instinct to use trait object anywhere they would've used a shared base class in some other language, but that's not usually the best pattern. Using concrete types with trait bounds (`&T .. where T: MyTrait` rather than `&MyTrait`), or inventing a new enum to hold all the types you expect, or even just trying to make ordinary composition work, is usually both easier and more performant.
- kibwen 9y ago> You can use any type of pointer to refer to a trait object, not just Box. In particular, you can use &T where T is a trait. You can, though due to the extra annotations required I don't suggest that people new to the language try to use trait objects with references. (Hell, I try to keep people new to the language away from trait objects entirely, they're pretty restrictive.)
- oconnor663 9y agoYeah I added a followup bit probably after you wrote your comment. My suspicion now is that parent is running into the "C++ inheritance -> Rust trait objects" mismatch, and that they should probably try to use something else.
- dragonwriter 9y ago> 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.
- deleted 9y ago[deleted]
- TheCoelacanth 9y ago> What I really meant was that you can't have Vec<T> where T is a trait without also wrapping that in a box In what language can you do something like that? In C++, it would compile but anything that you put in the vector would get sliced down to the base type. In Java, everything you put in would get boxed.