Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clarify the recursion problem of struct and the consistency guarantee of RefCell #152

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 39 additions & 37 deletions borrowed.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,71 +45,73 @@ fn foo() {
```

Like values, borrowed references are immutable by default. You can also use
`&mut` to take a mutable reference, or to denote mutable reference types.
Mutable borrowed references are unique (you can only take a single mutable
reference to a value, and you can only have a mutable reference if there are no
immutable references). You can use a mutable reference where an immutable one is
wanted, but not vice versa. Putting all that together in an example:
`&mut` to take a mutable reference, or to denote mutable reference types. There are 3 levels of mutability here:

- The data, defined by `let x = 5` or `let mut x = 5`.
- The reference, defined by `let xr = &x` or `let xr = &mut x`.
- The binding of the reference, defined by `let xr = &x` or `let mut xr2 = &x`. Its mutability defines whether the reference can be changed to point to another value.

The reference can only be mutable if the data is mutable, as the mutability of the reference suggests whether the data can be modified through the reference.
This is similar to unique pointers.
The mutability of the binding of the reference is independent of the mutability of the reference itself or the data.
This is similar to C++ where pointers can be const (or not) independently of the data they point to.
This is in contrast to unique pointers, where the mutability of the pointer is linked to the mutability of the data.

```rust
fn bar(x: &i32) { ... }
fn bar_mut(x: &mut i32) { ... } // &mut i32 is a reference to an i32 which
// can be mutated
fn bar_mut(x: &mut i32) { ... } // &mut i32 is a reference through which
// the i32 data can be mutated

fn foo() {
let x = 5;
//let xr = &mut x; // Error - can't make a mutable reference to an
// immutable variable
let xr = &x; // Ok (creates an immutable ref)
let mut xr = &x; // Ok (creates a mutable binding to an immutable ref)
let xr = &x; // Ok (creates an immutable binding to an immutable ref)
//xr = &mut y; // Error xr is immutable
bar(xr);
//bar_mut(xr); // Error - expects a mutable ref

let mut x = 5;
let xr = &x; // Ok (creates an immutable ref)
//*xr = 4; // Error - mutating immutable ref
//let xr = &mut x; // Error - there is already an immutable ref, so we
// can't make a mutable one

let mut x = 5;
let xr = &mut x; // Ok (creates a mutable ref)
*xr = 4; // Ok
//let xr2 = &x; // Error - there is already a mutable ref, so we
// can't make an immutable one
//let xr2 = &mut x; // Error - can only have one mutable ref at a time
*xr = 4; // Ok, x is mutable through xr

bar(xr); // Ok
bar_mut(xr); // Ok
}
```

Note that the reference may be mutable (or not) independently of the mutableness
of the variable holding the reference. This is similar to C++ where pointers can
be const (or not) independently of the data they point to. This is in contrast
to unique pointers, where the mutableness of the pointer is linked to the
mutableness of the data. For example,
Mutable borrowed references are unique (you can only take a single mutable
reference to a value, and you can only have a mutable reference if there are no
immutable references). You can use a mutable reference where an immutable one is
wanted, but not vice versa. Putting all that together in an example:

```rust
fn foo() {
let mut x = 5;
let mut y = 6;
let xr = &mut x;
//xr = &mut y; // Error xr is immutable
let xr = &x; // Ok (creates an immutable ref)
//*xr = 4; // Error - mutating immutable ref
//let xr = &mut x; // Error - there is already an immutable ref, so we
// can't make a mutable one

let mut x = 5;
let mut y = 6;
let mut xr = &mut x;
xr = &mut y; // Ok

let x = 5;
let y = 6;
let mut xr = &x;
xr = &y; // Ok - xr is mut, even though the referenced data is not
let xr = &mut x; // Ok (creates a mutable ref)
*xr = 4; // Ok
//let xr2 = &x; // Error - there is already a mutable ref, so we
// can't make an immutable one
//let xr2 = &mut x; // Error - can only have one mutable ref at a time
}

```

If a mutable value is borrowed, it becomes immutable for the duration of the
borrow. Once the borrowed pointer goes out of scope, the value can be mutated
again. This is in contrast to unique pointers, which once moved can never be
used again. For example,
If a mutable value is borrowed in the same scope, the original value (`x` above) becomes immutable while being borrowed (by `xr` above).
`x` can only be changed through `xr`.
If a mutable value is borrowed in a different scope, it becomes immutable while in that scope.
This is to ensure at any given time, there is only one mutable reference to a value, including the original value itself.

Once the borrowed pointer goes out of scope, the value can be mutated again.
This is in contrast to unique pointers, which once moved can never be used again. For example,

```rust
fn foo() {
Expand Down
43 changes: 26 additions & 17 deletions data-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,12 @@ fn foo(sos: SomeOtherStruct) {
Structs cannot be recursive; that is, you can't have cycles of struct names
involving definitions and field types. This is because of the value semantics of
structs. So for example, `struct R { r: Option<R> }` is illegal and will cause a
compiler error (see below for more about Option). If you need such a structure
compiler error (see below for more about Option).
One way to think about this is that the compiler needs to infer the size of a struct at compilation.
If a struct contains itself, there would be a self-contradiction in the size inference:
The size of `R` needs to be the same as one of its memory fields, but at the same time adds size overhead on top of that field (larger than one field).

If you need such a structure
then you should use some kind of pointer; cycles with pointers are allowed:

```rust
Expand All @@ -66,8 +71,9 @@ struct R {
}
```

If we didn't have the `Option` in the above struct, there would be no way to
instantiate the struct and Rust would signal an error.
Note that using a pointer `Box` here alone solves the size inference problem but is not enough.
Without `Option`, following the decomposition of `R` depending on another `R`, the regress would be infinite.
There must be eventually an `R` that does not depend on another `R` to break the cycle.

Structs with no fields do not use braces in either their definition or literal
use. Definitions do need a terminating semi-colon though, presumably just to
Expand All @@ -87,8 +93,7 @@ Tuples are anonymous, heterogeneous sequences of data. As a type, they are
declared as a sequence of types in parentheses. Since there is no name, they are
identified by structure. For example, the type `(i32, i32)` is a pair of
integers and `(i32, f32, S)` is a triple. Tuple values are initialised in the
same way as tuple types are declared, but with values instead of types for the
components, e.g., `(4, 5)`. An example:
same way as tuple types are declared, in the sense that you just directly put values into a pair of parentheses. An example:

```rust
// foo takes a struct and returns a tuple
Expand All @@ -108,7 +113,6 @@ fn bar(x: (i32, i32)) {

We'll talk more about destructuring next time.


## Tuple structs

Tuple structs are named tuples, or alternatively, structs with unnamed fields.
Expand Down Expand Up @@ -149,7 +153,7 @@ fn foo() {
```

However, Rust enums are much more powerful than that. Each variant can contain
data. Like tuples, these are defined by a list of types. In this case they are
data. Like tuples, each variant is identified by a list of types. In this case they are
more like unions than enums in C++. Rust enums are tagged unions rather than untagged unions (as in C++).
That means you can't mistake one variant of an enum for another at runtime[^1]. An example:

Expand Down Expand Up @@ -223,7 +227,7 @@ Here, the parent field could be either a `None` or a `Some` containing an
you usually would.


There are also convenience methods on Option, so you could write the body of
There are also convenient methods on Option, so you could write the body of
`is_root` as `node.parent.is_none()` or `!node.parent.is_some()`.

## Inherited mutability and Cell/RefCell
Expand Down Expand Up @@ -308,14 +312,17 @@ is not much to go wrong.

Use RefCell for types which have move semantics, that means nearly everything in
Rust, struct objects are a common example. RefCell is also created using `new`
and has a `set` method. To get the value in a RefCell, you must borrow it using
the borrow methods (`borrow`, `borrow_mut`, `try_borrow`, `try_borrow_mut`)
these will give you a borrowed reference to the object in the RefCell. These
methods follow the same rules as static borrowing - you can only have one
mutable borrow, and can't borrow mutably and immutably at the same time.
However, rather than a compile error you get a runtime failure. The `try_`
variants return an Option - you get `Some(val)` if the value can be borrowed and
`None` if it can't. If a value is borrowed, calling `set` will fail too.
and has a `set` method.

To **get** the value in a RefCell, you must borrow it using
the borrow methods (`borrow`, `borrow_mut`, `try_borrow`, `try_borrow_mut`).
However, to **set** the value in a RefCell, you must call `set` directly on a `RefCell`.
This requirement ensures a strong consistency guarantee---read repeatability[^3]:

1. `set`, itself an atomic operation, ensures that the value is not borrowed (mutably or immutably) before changing it.
2. Modification through mutable reference is controlled by borrowing rules which are enforced at runtime: Only one mutable reference can exist at a time.

The `try_` variants return an Option - you get `Some(val)` if the value can be borrowed and `None` if it can't.

Here's an example using a ref-counted pointer to a RefCell (a common use-case):

Expand All @@ -335,7 +342,7 @@ fn foo(x: Rc<RefCell<S>>) {
}

let mut s = x.borrow_mut(); // OK, the earlier borrows are out of scope
s.field = 45;
s.field = 45; // mutable reference s allow x to be modified through s
// println!("The field {}", x.borrow().field); // Error - can't mut and immut borrow
println!("The field {}", s.field);
}
Expand All @@ -358,3 +365,5 @@ locking is better since you are more likely to avoid colliding on a lock.
[^1]: In C++17 there is `std::variant<T>` type that is closer to Rust enums than unions.

[^2]: Since C++17 `std::optional<T>` is the best alternative of Option in Rust.

[^3]: Read repeatability means that one transaction (in the database world) or one owner within a scope (in Rust world) can read the same value multiple times and get the same result each time. This means that no other transaction or owner can change the value in between reads.
2 changes: 2 additions & 0 deletions unique.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ fn foo() {
As with primitive types in Rust, owning pointers and the data they point to are
immutable by default. Unlike in C++, you can't have a mutable (unique) pointer to
immutable data or vice versa. Mutability of the data follows from the pointer.
Correspondent with this design, mutability of the pointer means not only whether that pointer can point to another memory address and **disown** the original data, but also whether the original data may be modified.
E.g.,

```rust
Expand All @@ -68,6 +69,7 @@ fn foo() {
let mut x = Box::new(75);
x = y; // OK, x is mutable.
*x = 43; // OK, *x is mutable.
x = 76; // ERROR, type mismatch.
}
```

Expand Down