15 ms·
What's a reference in Rust?
- eximius 9y agoOverall really great. > These 3 types all have equivalent reference types (again: a reference is a pointer to memory in an unknown place): &[T] for Vec<T>, &str for String, and &T for Box<T>. This seems to accidentally imply that these reference types are for things on the heap. i.e., that &T is borrowed equivalent to Box<T> which is not true. All three of these reference types can point to memory not on the heap. The former two 'usually' don't, while the latter will vary wildly depending on the application.
- gpm 9y ago&[T] are commonly created from stack allocated arrays, and &str are even more commonly created from read only string literals... so I don't think it's correct so say that those "usually" point to things on the heap. (But of course the definition of "usually" could vary, it wouldn't shock me to find out they did 60% of the time). Or did you mean &T usually points to things on the heap, in which case I should just say it very very commonly points to stack allocated things as well.
- danieldk 9y ago&[T] are commonly created from stack allocated arrays, Really? I would say that in my typical Rust code &[T] is created from a heap-allocated array >90% of the time. Most functions that do not require ownership of an argument will use &[T] and not &Vec<T> (or perhaps S: AsRef<[T]>), since &[T] works for stack and heap memory and &Vec<T> is automatically converted to &[T] through Deref coercion. E.g.: fn main() { let v = vec![1, 2, 3, 4, 5]; blah(&v); } fn blah<T>(s: &[T]) { println!("{}", s.len()); } (The same is true for &str.)
- gpm 9y agoWhen you pass a `Vec<T>` directly to a non-mutating function or method not implemented on `Vec<T>` itself you pass it as a `&[T]`. But more often I pass it as part of a struct so it remains as (indirectly) `&Vec<T>`. However pretty much whenever you use a stack allocated array you use it as a &[T], part of a struct or not. I'm sure I use a heap allocated &[T] more often, but I doubt it reaches 90%. For &str you have to remember that every string literal in your program is one. When you do `some_String.starts_with("/mnt")`, `println!("hi there {}", name)`, etc you are using a new &str. I suspect most programs use more static strings than dynamic Strings (particularly since Rust isn't heavily used in GUIs yet).
- codefined 9y agoAs someone who has just spent the last 15 minutes escaping to HN from Rust due to reference errors, this was amazingly useful and actually helped me fix the error I was getting.
- gamegoblin 9y agoIf you ever find yourself stuck for too long, drop a code snippet into https://play.rust-lang.org/ https://play.rust-lang.org/ and share a link to the code in IRC channel at #rust or #rust-beginners (irc.mozilla.org). Very friendly community.
- gjtorikian 9y agoI second this. I asked a bunch of dumb questions on IRC, Reddit, and the forums and every single time the responses were so patient and helpful. I work at GitHub and I’ve been telling people that for the future of open source we really ought to be looking at the Rust community, both the amount of automation they have and also their general communication style.
- CleanCut 9y agoYay! I start at Github on Dec 5th, and I'm a huge Rust enthusiast.
- gkaemmer 9y agoFunny, I just had the exact same experience. Had been fixing compiler errors by adding lifetimes to my structs instead of boxing the values. I need a good reference on the right way to un-learn certain C concepts to make learning Rust concepts easier.
- pornel 9y agoIt's a very important topic to understand to be productive in Rust. My knowledge of C made learning Rust so much harder for me. It's really hard to stop thinking in pointers. While Rust's references are technically implemented as pointers, for the purpose of "fighting with the borrow checker" it makes more sense to think of them as read/write locks for regions of memory.
- bjz_ 9y agoYeah, interestingly I think it's hard to understand what is going on with C because `T*` pointers can be used for many things. I found it easier to go to C after doing Rust because I had a deeper understanding of the semantics behind them. Was frustrating though because it never caught my mistakes!
- greenhouse_gas 9y agoMe too. I found *&var quite confusing.
- shmerl 9y agoC++ helps more than C for Rust.
- Paul-ish 9y agoI spent a whole day struggling with a bug because I equated &[T] with *T and thought you could cast one to the other (for interop with C++ code). It took me too long to figure out that &[T] is two words long, but now its obvious. I'm not sure where I thought the "length" part was being stored.
- TheDong 9y ago> Every struct (or at least every useful struct!) refers to data Not true, zero-sized structs are quite useful too. They can be used to fulfill traits, indicate certain errors (often in enums), etc. A couple quick examples from the stdlib: https://github.com/rust-lang/rust/blob/1.22.1/src/libstd/sync/mpsc/mod.rs#L579-L581 https://github.com/rust-lang/rust/blob/1.22.1/src/libstd/syn... https://github.com/rust-lang/rust/blob/1.22.1/src/libstd/collections/hash/map.rs#L31-L33 https://github.com/rust-lang/rust/blob/1.22.1/src/libstd/col... I recommend watching this excellent rustconf 2017 talk for more information; it heavily features information on how zero-sized types can be used: https://www.youtube.com/watch?v=wxPehGkoNOw https://www.youtube.com/watch?v=wxPehGkoNOw
- deleted 9y ago[deleted]
- sagichmal 9y agoBy opening with "Not true" you're establishing a contrarian position, which puts people -- likely the author, potentially even the reader -- on the defensive, emotionally. It's sufficient and actually a lot nicer to simply state your point: e.g. "Zero-sized structs are quite useful too."
- littlestymaar 9y agoI agree for the author's point of view, but as a reader I enjoy contradiction and argumentation because that's where I learn most. Then when I see someone starting with `not true` or `I disagree`, I immediately interested in reading more. YMMV though.
- burntsushi 9y agoGreat post! I appreciate the socratic style. I agree with other posters that stuff like this is important to be comfortable with when writing Rust, and more material like this blog post is fantastic. I think if I were to write a part 2 of this blog post, it would be about learning how to read Rust code such that you know what is a reference and what isn't, and more pointedly, when something is behind two references. These things are important for effectively using pattern matching among other things. With that said, I'd like to add some advice by spring-boarding off a part of the post. > Converting from a Vec<T> to a &[T] is really easy – you just run vec.as_ref(). The reason you can do this conversion is that you’re just “forgetting” that that variable is allocated on the heap and saying “who cares, this is just a reference”. String and Box<T> also has an .as_ref() method that convert to the reference version of those types in the same way. While on the surface this is absolutely correct, there is a subtle point missing here: as_ref on Vec/String/Box is implemented as part of the AsRef[1] trait, which is _intended_ for use in generic programming. Aside from intent, practically speaking, using as_ref in a non-generic context can often be somewhat unergonomic, since depending on how you use it, it might require a type annotation (because it's generic!). Where AsRef is useful is in making the types of parameters to functions a bit more liberal. One particularly convenient place where it's used in the standard library is for defining functions that accept file paths. For example, the type signature of the function that opens a file is[2]: fn open<P: AsRef<Path>>(path: P) -> Result<File> Basically, this function says that it accepts a parameter `path` with a type `P` that can be infallibly converted into a `Path`. Why is that convenient? Because lots of useful types implement `AsRef<Path>`. They include OsStr, Cow<'a, OsStr>, OsString, str, String, PathBuf, and of course, Path itself. This is what let's you write `File::open("foo/bar")`. Without the generic `AsRef<Path>` constraint, the signature would look like this: fn open(path: &Path) -> Result<File> Which would mean that you'd need to write something like `File::open(Path::new("foo/bar"))` instead. So what's the alternative to using `as_ref` if I'm here poo-pooing it? In my experience, the typical thing to do here is to rely on something called deref. That is, if `s` is a `String` then `{STAR}s` is a `str` and `&{STAR}s` is a `&str`. In many cases, the explicit dereference (so that's `&s` instead of `&{STAR}s`) can be elided and the compiler will "auto-deref" for you. For example, given a function like the following fn repeat(string: &str, count: u64) -> String and a string `s` with type `String`, then repeat(&s, 5) will "just work." If you prefer the explicit, then I think the recommendation is to use type specific conversion methods. For `Vec<T>`, `as_slice` will give you a `&[T]`. For `String`, `as_str` will give you a `&str`. OK, that's enough for now! This rabbit hole goes deeper, but I'll stop here. :) > One question I have (that I think I will just resolve by getting more Rust experience!) is – when I write a Rust struct, how often will I be using lifetimes vs making the struct own all its own data? If I were forced to give a pithy answer to this question, then I think I would say (predominantly from the perspective of a library writer): "It's a healthy mix, but if I don't care about performance for $reasons, I can usually ignore lifetimes in the types I define." [1] - https://doc.rust-lang.org/std/convert/trait.AsRef.html https://doc.rust-lang.org/std/convert/trait.AsRef.html [2] - https://doc.rust-lang.org/std/fs/struct.File.html#method.open https://doc.rust-lang.org/std/fs/struct.File.html#method.ope...
- kibwen 9y ago> In Rust, a boxed pointer sometimes includes an extra word (a “vtable pointer”) and sometimes don’t. It depends on whether the T in Box<T> is a type or a trait. Don’t ask me more, I do not know more. For those wanting to know more about this, the idea is that types whose size is unknown at compile-time receive this two-word representation. I tend to refer to these as "fat pointers", which is terminology from Cyclone (though Cyclone's fat pointers serve a different purpose). More documentation on these can be found at https://doc.rust-lang.org/beta/nomicon/exotic-sizes.html#dynamically-sized-types-dsts https://doc.rust-lang.org/beta/nomicon/exotic-sizes.html#dyn... and in the section in the book on slices (terminology taken from Go, whose slices are similar though with an extra word) https://doc.rust-lang.org/book/second-edition/ch04-03-slices.html#string-slices https://doc.rust-lang.org/book/second-edition/ch04-03-slices...
- saghm 9y ago> terminology taken from Go, whose slices are similar though with an extra word Interesting; I've always thought of Rust slices as being rather different from Go slices. A Rust slice is always used through a reference and doesn't own its data, whereas a Go slice is not generally used through a pointer and sometimes points to a heap allocated section of memory, so it's basically the union of Rust's vector and slices. Tangentially, the inability to tell whether some value is heap allocated or not from the type is one of my main gripes when working with Go as opposed to Rust; in Rust, I can be sure that `Vec`, `String`, `Box`, `Rc`, and `Arc` are all heap allocated and that slices, arrays, `str`, `&T`, and `&mut T` are not. In Go, slices and pointers might be heap allocated--or they might not.
- sriram_malhar 9y ago> the inability to tell whether some value is heap allocated or not from the type is one of my main gripes when working with Go Why is this important? The whole point of GC is to not spend time debating this point, as long as performance is good enough
- jmgao 9y ago
- PaulBGD_ 9y agoFantastic explanation, coming from a C background I definitely got confused by some of the lifetime things that Rust does.
- tempodox 9y agoFor me, the question in Rust is not, what's a reference. But how do I find all functions applicable to a given type? In C/C++, I can just grep the header files for the type name and voilà. I find header-less languages like Rust or Swift really obscure in that way.
- kibwen 9y agoI'm a bit confused, can you be more specific as to what you're asking for? grepping for types works just as well for headerless languages as it does for C++, though "finding all functions applicable to a given type" can't be done via grep for either C++ or Rust given that generics exist.
- crispweed 9y agoI guess tooling can help here? It seems like this information is all collected together for the docs, for example. Look at the page for std::vec::Vec, for example (https://doc.rust-lang.org/std/vec/struct.Vec.html https://doc.rust-lang.org/std/vec/struct.Vec.html). Here, you have sections for: 'Methods', 'Methods from Deref<Target=[T]>' and 'Trait Implementations', and then it seems that if you look through all these sections, you can see everything that can be called on this type, highlighted in the same light brown colour. It would be quite nice to get an alphabetically ordered list of just these method names, also..
- MaulingMonkey 9y agoHeader diving certainly works well for some C/C++ codebases, but not all I've sadly discovered. The rough analog in Rust might be grepping rustdoc generated documentation, which should at least generally tell you what exists / is publicly exposed. Grepping the full source with extra filters like \bfn\b or \bpub fun\b might be another option. Like C++, you can also (ab)use intellisense to find a lot of them as well. I should hack more on Visual Rust to improve the situation there...
- pjmlp 9y agoBy making use of an IDE or tools like RLS.
- simias 9y ago
- tybit 9y agoSo in comparison to C++ would it be correct to say that Box<T> is like unique_ptr<T>, Vec<T> is Vector<T>, and that references are the same in both languages?
- kibwen 9y agoRust's Box and Vec are analogous to C++'s unique_ptr and vector, yes. But references in Rust really aren't anything like C++ references, given that Rust references 1) are first-class, 2) come in two varieties (mutable/exclusive and immutable/shared), 3) feature mechanically-checked lifetimes, and 4) will be two words in size (rather than one) if the underlying type is dynamically-sized.
- jcelerier 9y ago> Rust references 1) are first-class what makes them more first-class than C++ references ? eg in C++ given a type T, you can use `std::add_lvalue_reference<T>`, `std::remove_reference<T>`, overload on references, check if a type is a reference to another...
- Sharlin 9y agoC++ reference types are first-class. But instances of reference types are not first-class values. References are not objects, in standard speak, they do not have a memory location, you can't take their address, you can't pass them to functions (a reference parameter means passing a value by reference, not a reference by value). And so on. Rust references are more like C/C++ pointers or Java references in that they are actual values, and AFAIK Rust functions, like Java and C functions, are strictly pass-by-value.
- wyldfire 9y agoThis is a really helpful distinction, thanks for clarifying.
- kimundi 9y agoI think they just meant that Rust references are like normal first-class generic types. Eg, you can nest them to get a &mut &T for example, since they behave more like a pointer in that regard. C++ references on the other hand are more like modificators of a type, eg you can have a T or a T&, but having a (T&)& does not make sense. (Outside of templates, where it gets folded down to a T&.)
- crispweed 9y ago> The most important thing about Rust (and the thing that makes programming in Rust confusing) is that it needs to decide at compile time when all the memory in the program needs to be freed. > ... > When the function blah returns, x goes out of scope, and we need to figure out what to do with its my_cool_pointer member. But how can Rust know what kind of reference my_cool_pointer is? Is it on the heap? > ... > If we knew that my_cool_pointer was allocated on the heap, then we would know what to do when it goes out of scope: free it! The way this is written kind of seems to suggest that Rust will sometimes free heap memory when a reference to that memory goes out of scope, which I think is misleading. As I understand it, this is not the case, and the point is just that Rust needs to be able to prove that nothing else freed the referenced heap memory at any point where the reference may be used.
- jmite 9y agoNo, I think the article is right. When a value goes out of scope, its drop method is called, which for Box values deallocates it. The trick is that if it is (possibly) returned from a function, it is moved instead of dropped. It's also important to distinguish Box from Rc. Both are heap values but have very different behavior.
- crispweed 9y agoThe text I quoted seemed to suggest that the reference going out of scope could trigger deallocation.
- Manishearth 9y agoWhich is true. The word "reference" is overloaded, it can be used to mean "anything pointery that's guaranteed to exist" too. Box<T> in this context is a reference. The post does kind of dance between definitions of "reference" a bit, but I think that's intentional.
- lmm 9y agoYeah. Knowing that it's on the heap isn't enough - we also have to know whether we own it or are borrowing it.
- shock 9y ago> I know in Java you have boxed pointer versions of primitive types, like Integer instead of int. And you can’t really have non-boxed pointers in Java, basically every pointer is allocated on the heap. That's not true. In Java pointers can very well be allocated on the stack, but the objects that they point to will be on the heap
- Rusky 9y agoIt looks like the article pretty consistently uses the phrase "the pointer is allocated ..." to mean that that's where it's pointing to.
- shock 9y agoSo the article is pretty consistently misleading/incorrect. A pointer is a data structure like any other, in fact Java is pass-by-value, the pointer values are copied when objects are being passed as function arguments.
- Rusky 9y agoTo me it seems it's just using different terminology than you expected. I've heard and used the article's version plenty of times and it generally works in context.
- b0rsuk 9y ago> I’ve written a few hundred lines of Rust over the last 4 years, but I’m honestly still pretty bad at Rust and so my goal is to learn enough that I don’t get confused while writing very simple programs. This makes me feel hopeless, as I'm only about to start using Rust in my hobby projects after reading the essential book chapters. I hope it's just excessive humility on her part ? At the same time, I'm excited because if I commit myself to mastering such a language it can make me stand out. I still have an opportunity to be an early adopter, and have a head start in a promising new language.
- oconnor663 9y agoThe IRC channel and the r/rust subreddit are all very helpful for new rustaceans who get stuck, so don't hesitate to reach out.
- littlestymaar 9y agodon't forget stackoverflow ! It doesn't have all the answers easy to google, but /u/shepmaster is doing an amazing job as a curator here. You usually get an answer in less than half an hour (assuming he's awake, but I'm not even sure he sleeps :p)
- agmcleod 9y agoReally just this year is the first time i've been using it for larger side projects. While I still run into some things with the borrow checker, I find I'm much better at predicting them, and figuring out a strategy around it. Really once you get through the book and are comfortable with types, you really just need to start working on something bigger. You will need to change things and refactor as your original ideas don't pan out. But you learn from it. Good luck!
- kibwen 9y agoA few hundred lines of the course of four years would imply that the author is idly dabbling with Rust rather than using it in anger. (It also implies that she's been using Rust since before its 1.0 release, which would probably make it harder to get a handle on modern Rust, as it changed significantly back then.) Trust me, it won't take you anywhere close to four years to get proficient in Rust. :)