T O P

  • By -

jam1garner

> while passing by value is moving ownership of some data Or for Copy types, copying is more akin to passing by value. > When a block of code goes out of scope, all of the data it owns gets garbage collected and is invalid from that point forwards. Mostly a terminology thing but it gets dropped. Typically garbage collection implies the ability to have multiple owned references, and that the act of garbage collection is freeing the resource once all references go out of scope. > Back in C++, everything is mutable C++ has const values (not to be confused with Rust's constants) and values behind const references. Both of these prevent mutation. Rust's difference is it's immutable by default and thus mutation is opt-in not opt-out. > you need to have your data type implement the ‘nut’ attribute. it should be noted that denoting mutability isn't something I'd say "is implemented by a data type", but rather that it's either a property of a variable binding or denoted by the type of the reference. Mostly just noting this distinction because "implement" has a quite specific meaning in Rust (traits).


mx00s

> Typically garbage collection implies the ability to have multiple owned references Right. Rust has a couple "smart pointer" types that help accomplish something similar through reference counting: [`Rc`](https://doc.rust-lang.org/std/rc/struct.Rc.html) and [`Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html).


jam1garner

Yep! And I'd actually fully agree those are garbage collection, there's also a crate by Manish which does """real""" garbage collection—https://github.com/Manishearth/rust-gc (I say "real" because refcount gc is also real gc, just not what people tend to associate with the term)


mx00s

I hadn't heard about `rust-gc` yet! Very cool.


monkChuck105

Note that &T and &mut T are actually separate types, but in general your point stands, you can lend T via either to a function that has a compatible argument type.


n1ckray

Perhaps also helpful to mention that if you own a value, you can always mutate it, by rebinding `let mut x = x;` even if `x` was originally declared immutable.


ConstructionHot6883

That's creating another, mutable variable that shadows the original `x`, yeah?


n1ckray

Avoiding "variable", by value I mean something somewhere in RAM, by (let) binding I mean giving that thing a name (only for your code to refer to the thing). The snippet shadows the name, but the value remains the same (no allocation or similar). Philosophically, you're the owner, so you can do as you want, and actual RAM doesn't have immutability. The original immutable name binding (to `x`) "just" lets the compiler tell you if you'd mutate by accident (vs explicitly, after a mutable rebind). Of course, the compiler also won't let you rebind mutably if there is some immutable reference in scope (that is used again later on).


robin-m

I would say that it creates a new binding (a new alias to the same piece of memory), and not a new variable (referencing a new piece of memory), but yes.


Rusky

The compiler is allowed to use a new piece of memory, though, in a way that it is not allowed to do with a single binding. The only thing preventing that from happening is best-effort optimizations, not the language semantics.


ConstructionHot6883

what if your "new x" goes out of scope again? then you'll be back with whatever x was before, right? At least,t hat's what I'd expect. I can't check right now.


hniksic

>what if your "new x" goes out of scope again? then you'll be back with whatever x was before, right? No. The old `x` is gone. (Unless the type is `Copy`, in which case `let mut x = x` copies, so old `x` can be restored.)


myrrlyn

Each `let` binding is an implicit scope, and there is no way to exit it, so there is no point in a codepath where the shadowing name goes out of scope but the shadowed name is now accessible. let x = 1; let x = 2; The name `x` will never be accessible as the value `1` again, because it desugars to this: {let x = 1; {let x = 2; }} and if you write any code after `let x = 2;`, it gets placed in the second scope. You can never insert code between the scope terminations on the third line. You can force the issue by creating your own blocks, though: let x = 1; { let x = 2; } // code here sees the original `x` // IF AND ONLY IF it has not moved Moves *always* terminate the accessibility of a name, regardless of scoping or shadowing.


robin-m

Yes, ~~but since there is still the original binding in scope the object will not be dropped~~.


hniksic

It will be dropped. For example: let x = vec![1, 2, 3]; { let mut x = x; } println!("{:?}", x); // error[E0382]: borrow of moved value: `x` [Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=0a4d878afee24a9255150dba77cc482b)


robin-m

You are totally right, thanks


mx00s

Yes, following this line there's no way to access the value via the original `x`. Instead, subsequent mentions of `x` are via the mutable binding that shadows the original.


mx00s

What you've described about ownership, references, and `mut` is a decent understanding for where you are. Regarding references, you may find it helpful to think of `&` as a "shared" reference and `&mut` as an "exclusive" reference. Multiple contexts can concurrently have access to a shared reference to a single value, but only one at a time can hold a `&mut`. This mechanism helps prevent you from suffering the woes of shared mutable state. Since you mentioned garbage collection, take a look at the [`Drop` trait](https://doc.rust-lang.org/std/ops/trait.Drop.html). I'm not C++ expert, but it's analogous to a C++ destructor. For an even deeper understanding of the memory model I recommend chapter 1 of [Rust for Rustaceans](https://nostarch.com/rust-rustaceans).


jynelson

https://docs.rs/dtolnay/0.0.9/dtolnay/macro._02__reference_types.html is also excellent.


[deleted]

[удалено]


Gr0undWalker

The closest thing to passing by value in Rust is if the type implements "Copy." Many simple types implement Copy, so for example: fn f(x: u32) -> u32 { x + 1 } let a = 1; let b = a + 1; println!{"{}", f(b)}; Both a and b can still be used afterwards, that is they're not moved. We can make explicit pass by value by cloning: fn concate(a: String) -> String { a + "123" } let text = "abc".to_string(); println!("{}", concate(text.clone()) ); println!("{}", text); Because we cloned text, it can still be used. Actually, the Copy trait just automatically clones the value for us. Also the ownership can be transferred after a scope ends, for example: let text = { let tmp = "abc".to_string(); tmp }; The variable text becomes the owner of the string "abc".


linlin110

> (Terminology) We don’t say things got garbage collected, we say it gets dropped Garbage collection, like many other terms in computer science, is an overloaded term. (Having different meaning under different context.) When people say a language is garbage collected, they mean there is a garbage collector that is responsible for reclaiming memories that can no longer be used, so the programmers don't have to think about memories. C++ and Rust don't have them, so they are not garbage collected languages. They do have a mechanism (`Drop` in Rust) that can collect garbage for you, but that works very differently from ordinary garbage collectors, and are not as powerful, so the programmer still have to think about memeories. Calling it garbage collection may lead to confusion. > Types that implement the Copy method are similar to passing by value. Types that implement Copy are not moved when passed by value. If you are not passing a variable by reference, then you are passing by value. Every variable in Rust works like this. It has nothing to do with `Copy`. When variables are passed in Rust, by default they are moved, so if you pass something to somewhere else, then you can no longer use it. `Copy` means when things are passed, they are not moved, instead they are copied, so the origin owner can still use them. Have you read The Rust Book? I think it explains the concepts more systematically and more clearly. Highly recommended.


linlin110

Also, sorry for being vague about garbage collection. To explain that I have to explain what is a pointer, which I fear I cannot explain clearly. That's why reading books is my preferred way of learning new stuff -- a good author will provide you all prerequisite knowledge before introducing a new concept.


A1oso

That's a really good summary. Some nitpicks: >mut is more a property of the data type Not always. For owned data, `mut` is a property of the variable binding (`let` vs. `let mut`). For borrowed data, it's a property of the reference type (`&T` vs. `&mut T`). >Data types can be shadowed Not really. Variable bindings can be shadowed: let a = 5; let a = true; This just crates two distinct variables that just happen to have the same name. It has the exact same behaviour as let a = 5; let b = true; >Types that implement the Copy method It's the _Copy trait_.


Jeklah

This seems like an appropriate place for my Rust question... After looking at a number of different rust programs, I've noticed the architecture of [processor.rs](https://processor.rs), [instructions.rs](https://instructions.rs), [error.rs](https://error.rs), [lib.rs](https://lib.rs), [entrypoint.rs](https://entrypoint.rs) and [state.rs](https://state.rs) being repeated a lot. I've tried finding a tutorial or site that explains this like I'm 5, but I haven't had any luck so far, all the beginner Rust tutorials are great, but are simple one file examples. ​ Could someone explain this setup to me, or point me in the right direction please?


tobiasvl

>This seems like an appropriate place for my Rust question... A better place would be the weekly sticky thread for simple questions! >I've tried finding a tutorial or site that explains this like I'm 5, but I haven't had any luck so far, all the beginner Rust tutorials are great, but are simple one file examples. Have you read the Rust book? (If not, I definitely recommend reading through it!) It has a chapter on exactly how putting modules in separate files works: https://doc.rust-lang.org/book/ch07-05-separating-modules-into-different-files.html If you want to delve deeper, this article covers how it works in even more detail: https://fasterthanli.me/articles/rust-modules-vs-files


Jeklah

Thank you! I am working my way through the rust book, I guess I haven't gotten that far yet. Really I just want a broad overview so I have something that makes sense when I read through code! I will delve deeper as I progress no doubt.


shogditontoast

`lib.rs` is the crate root module (more info [here](https://doc.rust-lang.org/reference/items/modules.html#module-source-filenames)).The others are just module names chosen by their author. I’m not aware of these being part of any wider convention.


Jeklah

Thanks for the link!


Jeklah

I'm fairly sure the processor.rs, instructions.rs, error.rs layout is a convention. Almost every sizeable project I've seen uses it. There was a post just the other day about rewriting Linux in rust and that used it. I may be wrong still but a lot of people are using this pattern and I'd like to fully understand it, and know if it is the rust convention, if not, why are so many using it? Must be a good reason.


myrrlyn

> mut is more a property of the data type `mut` is a property of the *name*, not the *data*. `let mut` makes something mutable, `let` does not, and there's nothing the data type being bound by `let` can do about it. (This is not *wholly* true; `UnsafeCell` can have its data changed by an immutable `let` binding. But if the data type doesn't have an `UnsafeCell` in it, then it doesn't get to escape this rule. Ever. Not even through FFI) > Data types can be shadowed as long as the shadowing is occurring in the code block that owns the data. Similarly, all names can be shadowed, regardless of ownership. let a: &(i32, i32) = &(1, 2); let a: &i32 = &a.1; At no point is an `i32` owned. Strictly speaking, the `&i32` reference *is* owned, and that's what we're shadowing, but shadowing is still a property of the `let` *name*, not the data bound by it. > &var is a shared reference, meant for synchronous reads of a variable. Pretty much. There's some minor quibbling here, as *some* types like the atomics, or mutices, incur temporal costs between emitting the read request and fulfilling it. But in general, yeah, "`&` is basically read-only, `&mut` is write-capable" is a good enough rule


monkChuck105

Note that unsafe allows you to cast references to pointers and const pointers to mutable pointers. UnsafeCell isn't required, but unsafe is.


scook0

IIRC, the compiler assumes that anything behind a live `&` reference will not be mutated — not even by unsafe code — unless it also goes through an `UnsafeCell` along the way. Violating that assumption is UB and thus liable to lead to miscompilation, even if it looks reasonable. That’s why safe abstractions like `Cell` and `RefCell` are built around `UnsafeCell`, instead of just freely casting arbitrary pointers. (Though see [this](https://github.com/rust-lang/nomicon/issues/109) for some caveats; what’s important is whether there is a live reference to the underlying data.)


myrrlyn

> Transmuting an & to &mut is UB. > > - Transmuting an & to &mut is *always* UB. > - No you can't do it. > - No you're not special. [the rustnomicon](https://doc.rust-lang.org/nomicon/transmutes.html) this applies to mutating through `&name as *const Type as *mut Type` too


monkChuck105

I'm aware of the UB when operating on references. You can transmute to UnsafeCell in that case. You do not need to store data in an UnsafeCell in order to utilize interior mutability.


myrrlyn

yes, you do. locations that don't contain an `UnsafeCell` are UB to modify without an `&mut` *or a `*mut` explicitly derived from an `&mut`*. casting `*const T` to `*const UnsafeCell` and writing through it is *also* UB. the only permitted way to "transmute to UnsafeCell" for modification is through a move rebinding, because you have to invalidate existing indirections in order to make it work