4 ms·
> You put everything in a Box::new (regular heap memory) or RC::new (reference counted memory) or Arc::new (reference counted memory suitable to be used concurr
by jbandela1 6y ago
> You put everything in a Box::new (regular heap memory) or RC::new (reference counted memory) or Arc::new (reference counted memory suitable to be used concurrently in different threads), and then when they go out of scope they'll be cleaned up.
Please don’t. This is probably one of the biggest mistakes I see in people coming from languages with garbage collection to languages like Rust or C++. They attempt to use smart pointers as a replacement for garbage collection.
The idiomatic use of these languages is that unless you have a good reason not to, just put the variable on the stack. This will make your program shorter, easier to reason about, and potentially faster due to memory locality.
The rough guideline I have heard for C++ which likely applies to Rust is that 90% of your variables should be on the stack. Of the remainder 90% should be unique ownership such as unique_ptr or Box. Shared ownership such as shared_ptr or Arc/Rc should ideally be no more than 1%.
- pbiggar 6y ago> Please don’t. This is probably one of the biggest mistakes I see in people coming from languages with garbage collection to languages like Rust or C++. They attempt to use smart pointers as a replacement for garbage collection. Thanks for the feedback. I'm new here, so the advice is appreciated. I've been using the rule of thumb "does the lifetime of this variable exceed the lifetime of this function" to decide whether to put it in an Rc. Given I'm implementing an interpreter for a GCed language, it seems like I'm pretty much going to have to put everything into an Rc, no? (Another commented mentioned an Arena, but either way it's not the stack).
- Jweb_Guru 6y agoFWIW, very often the answer in those "returning from a function" situations (in Rust anyway) is to allocate further up the stack, or clone, rather than use a smart pointer. In fact, you can nearly always do this, as long as you control enough of the involved code. Use of Rc is intended not only for data that outlives the function in which it's currently defined (because in general, you can define stuff wherever you want), but for data whose lifetime is unpredictable--there either isn't a safe point in your program where you can definitively drop it (this usually more applies to Arc, if it's holding data pointed at by two independent threads), or there is a known safe point but it is too imprecise to be useful to you. But the latter is pretty subjective; for instance, if you know it's safe to drop something when the program ends, but it might be safe to drop much earlier, in some contexts it still might be worth dropping at the end rather than dealing with the significant overhead of something like Rc or Arc. The reason I recommended arenas is that I find in compilers, often you have cases where you just need to define a scope at some point in your program way higher than when you create the value, and that's what they excel at--letting you initialize stuff with whatever parent scope you want, not arbitrarily restricting you to the function boundary.
- zozbot234 6y agoYou should only put an object in an Rc when its lifetime may potentially be determined by multiple, independent references. That's what it's for. For the simpler case of single ownership, Box<> is fine,
- saghm 6y agoYou can return stack values assuming you're willing to transfer ownership to the calling function. For example, this is totally valid (and does not heap allocate): struct Point { x: i32, y: i32, } fn add(p1: &Point, p2: &Point) -> Point { Point { x: p1.x + p2.x, y: p1.y + p2.y, } } You'll also find that a lot of common data structures already use the heap under the hood (e.g. Vec and HashMap), so there's no need to Box them. I'm not quite sure what you mean by "the lifetime of a function"; lifetimes are associated with references, but not with functions themselves. In particular, it can be totally valid to return a reference to something that needs to last beyond the current function call; lifetimes are used to enforce that you'll get a compiler error if the memory your reference points to won't survive long enough. For example, it's totally valid to have a function return a reference to a field on a struct as long as the struct was also passed in by reference: fn x_val(p: &Point) -> &i32 { &p.x } Since `p` is passed in by reference, the compiler knows that the reference to `x` will still be valid after the function returns, so it allows that reference to be returned. The compiler will even catch it if later you drop the Point while the reference to the `x` value is still alive: fn main() { let p = Point { x: 1, y: -1 }; let x: &i32 = x_val(&p); std::mem::drop(p); // Without this line, this will compile, since the compiler can tell that `x` isn't // used after `p` is dropped. However, since `x` is used here afterwards, the // compiler will generate an error stating that `p` does not live long enough. println!("{}", x); }
- seertaak 6y agoThe OP wanted to represent AST nodes - the stack won't do for that: the size of the object isn't known at compile time.
- ForLoveOfCats 6y agoI think that the parent commenter was referring to overuse of Box and reference counting. However the compiler does know the size as it is an enum (a sum type) which means it is effectively a discriminated union that the compiler forces you to check the type of.
- saghm 6y agoIf the enum is recursive (which will often be the case, since expressions can be nested in most languages), then you'll still need to use `Box` (or some other indirection) for some of the child nodes.
- deleted 6y ago[deleted]
- ForLoveOfCats 6y agoThat's a good detail to note, thanks. I wanted to note that the size of the enum itself is indeed known at compile time otherwise the compiler would complain (such as if it is improperly recursive). The point is mute though as a tree like this would usually be built on the heap anyway (whether that be an arena or normal allocation)
- pjmlp 6y agoThat is the only way when using libraries like Gtk-rs, unless you completely change the programming paradigm by using relm instead.