What is the definition of a reference?

Will I be right if I say that a Reference is a data type represented by the memory address where a specific piece of data is stored, be it on the stack, heap or static area. So, a Reference in Rust is treated as a safe pointer to a value in memory, because the type system and ownership mechanism ensure that references are properly managed, do not create memory leaks, and do not allow data access errors. Or is there a more precise definition?

Here is what the reference has to say about references:

https://doc.rust-lang.org/stable/reference/types/pointer.html#references--and-mut

And this is the section from the book about references and borrowing:

https://doc.rust-lang.org/stable/book/ch04-02-references-and-borrowing.html

2 Likes

It’s about right. The important difference from pointers or references in other language is that Rust references can only reference data that is already stored somewhere and initialized. They don’t own data by definition.

And also Rust can’t make referenced data live any longer, and can’t fix bad references. All it can do is keep rejecting programs with potentially invalid references, so it’s still up to the programmer to manage lifetime of references.

These behaviors are backwards in GC languages that allow programmers use references anywhere they like, with any ownership they want, and the GC makes data live longer to keep arbitrary references correct at run time.

6 Likes

Given that there is a reference type. Is the reference a data type or not?

Yes, references are primitive types.

2 Likes

Thanks very much!

1 Like

This is a wonderfully-concise description of what makes Rust great :+1:

Given that there is a reference type. Is the reference a data type or not?

Turning a data type T into &T creates a new "reference to T" data type in the same way that turning a data type T into Vec<T> creates a new "vec of T" data type. To illustrate this, just as one could create a Vec<Vec<Vec<T>>>, one can create a &&&T (although it wouldn't be particularly useful). So, in the sense that it's all data types, references are treated as the same sort of thing as everything else.

1 Like

In addition to the other replies, I'll add some less introductory details.

References as type constructors

@gretchenfrage pointed out how Vec<T> is a type constructor where you can "create new types", because it's parameterized by a generic type T. And they pointed out how references also have a generic type parameter, so they are also type constructors.

However, references actually have two generic parameters: the type parameter, and a lifetime parameter. So, for example, &'a mut String and &'b mut String are two different types unless the lifetimes 'a and 'b are the same. (Sometimes you can go from one lifetime to another, but that's a conversation for another day.)

This can be relevant as you learn about lifetimes and come to grip with borrow checker errors. Here's a good introductory video where it matters.

The fact that references are concrete types (once their parameters have been "filled in") means that when you have a generic T, it could end up being a &'a U for some concrete lifetime 'a and concrete type U. (It's a common newcomer misconception to think references aren't included in generic T.)

Unsized types and wide references

When T: Sized doesn't hold, we call T "unsized" or a dynamically sized type or a DST. References to DSTs are "wide" or "fat" references/pointers, which contain the pointer to the data, and some other piece of metadata about the unsized value. So far in Rust there are two types of metadata:

  • Pointers to slice types have an element count as a usize

    • &[T], &str, &Path, ...
  • Pointers to trait objects have a pointer to a static vtable containing method pointers and other metadata like the size and alignment of the erased type

    • &dyn Fn(), Box<dyn Iterator<Item = String>>, ...

Incidentally, when you introduce a generic type parameter T in angle brackets...

struct Foo<T> { ... }
impl<T> Foo<T> { ... }
fn foo<T: Clone>(t: T) { ... }
trait Trait<T> { ... }

...there's an implicit T: Sized bound. You can write T: ?Sized to remove the implicit bound.

fn bar<T: Clone + ?Sized>(t: &T) { ... }

So far, unsized values can't be passed, returned, or stored in local variables.

5 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.