about
10/21/2024
Post: Rust Data
Rust Data
bind, copy, borrow, move, clone, mutate
About
click header to toggle Rust Explorer
Synopsis:
1. Our goal is to understand the terms:
- | Bind: | Associate an identifier with a value |
- | copy: | Bind to a copy of the value of a blittable type, executed implicitly by compiler generated code, by copying bytes from source to destination location. Fast if the data is defined in a few bytes of storage, like the primitive types. |
- | borrow: | Create a named reference (pointer with special syntax and semantics) to an identifier's location. Pointers used for borrows must satisfy Rust's ownership rules, discussed in the Ownership Bite. Borrows are the only pointers that can be dereferenced in safe code. |
- | Move: | Transfer ownership of a type's resources, usually executed implicitly. Accomplished by creating, for the destination, a pointer to the source's resources (data allocated on the heap), and invalidating the source instance. That's fast, copying only a few bites. |
- | Clone: | Create a copy of a non-blittable type, invoked by program code. Slower than Move. |
- | mutate: | Change the value associated with a mutable identifier. |
2. Rust Types
A type is set of allowed values and operations that are legal for the set.
The Rust language defines a rich set of primitive types:
- | bool | |
- | char (utf-8) | |
- | integers: | i8, i16, i32, i64, isize, u8, u16, u32, u64, usize |
- | floats: | f32, f64 |
- | aggregates: | array: [T;N], slice: [T], str: literal string "....", tuple: (T1, T2, ...), struct { T1, T2, ... } |
- | String | a stack-based object holding a collection of utf-8 chars in the heap |
- | Vec<T> | very like a String, but holding a heap-based collection of an arbitrary type, T |
- | VecDeque<T> | stack-based object holding a heap-based collection of T objects with efficient access to both front and back |
- | Map<K, V>: | an associative container holding key-value pairs in the heap |
- | many more |
3. Binding to a Value
Bind - associate an identifier with a memory location
- A type is a set of legal values with associated operations.
-
Every identifier has a type:
let k: i32 = 42;
i32 is the type of a 32 bit integer. 42 is a value placed in the memory location associated with k -
Type inference:
let k = 42;
-
Code can explicitly specify the type of a value, e.g.:
let m = 42u8;
4. Binding to an identifier
Binding to an identifier has several forms:
Both sides of a binding expression must be of the same type. Rust will not implicitly convert an instance to
another type except for dereferencing smart pointer types.
-
let j:i32 = k; // makes copy for j because k is blittable -
let l = &k; // l makes a reference to k, called a borrow -
let s:String = "a string".to_string(); -
let t = s; // moves s into t, e.g., transfers ownership as s is not blittable
5. Assignment
x = y // copy if x and y are Copy types, y is valid after assignment t = s // move if s and t are Move types, s is invalid after assignment
6. Copy and Borrow
-
Copies happen implicitly when an identifier is bound to a Copy type:
let i = 3; let j = i; // copy j = i + 1; // copy -
Borrows happen when binding references to other identifiers:
let r = &i // borrow; &i , is just a pointer to the memory location bound to i. It cannot be reset, and is subject to Rust ownership rules, which are discussed in this Rust Bite.
7. Copy, Move, and Clone Traits
- To be eligible for the Copy trait they must be blittable.
-
The
str type represents immutable literal strings. Each is stored in static memory with program lifetime. They are always accessed with a reference, e.g.,s:&str = "a literal string" . So the reference is copied, as shown in Figure 2.
- Move types are non-blittable, with one exception, mutable references.
- Adding the Drop trait makes a Move type, even if blittable.
- When the thread of execution leaves a scope all move types, declared in that scope, are dropped, returning their resources with Drop::drop(). That's similar to a C++ destructor invocation.
- Data types with the Clone trait provide a clone() member function that creates a new instance of the type that has the same structure and copies of any resources held by the cloner.
- Examples of Clone types are the collections, e.g., Strings, Vecs, VecDeques, Maps, ...
8. Move and Clone
-
A move transfers a Move type's heap resources to another instance of that type.
-
The String,
s , shown in Figure 3. is moved tot with the statement:let t = s; // s is now invalid
- Move transfers ownership of resources.
-
The String,
-
A clone copies a Move type's heap resources to a new instance of that type.
-
The String,
s , shown in Figure 4. is cloned with the statement:let t = s.clone(); // s is still valid
- Clone operation copies resources to the target.
-
The String,
9. Mutation
By default, Rust data is immutable - it can't be changed. Code has to opt in
to mutation in order to change the value of an identifier. We do that with the
mut qualifier.
Mutability of data is an important part of Rust's
ownership policies, designed to ensure memory safety.
-
Immutable data:
let i = 1;
// i += 1; won't compile -
Mutable data:
let mut j = 1;
j += 1; // compiles since j is mutable
10. Traits Preview:
Trait | Applies to: | Examples | Consequences |
---|---|---|---|
Copy |
Single contiguous memory block ==> blittable |
ints, floats, aggregates of Copy types | Copys value from one memory location to another. Source valid after copy |
"Move" |
non-contiguous block ==> not blittable |
Strings, Vecs, VecDeques, ... stack-based aggregates managing instances in the heap |
Transfers data ownership to another identifier. Source invalid after move Attempt to use "Moved" variable is compile error. |
Clone | most types | Structs, Strings, Vecs, VecDeques, ... | Makes copy of resources for another identifier. Source valid after clone |
11. Formatting Data
12. Conclusions:
13. Exercises:
In order to build and run with cargo from the Visual Studio Code terminal you need to open
VS Code in the package folder for the code you want to build and run. That's the folder where
the package cargo.toml file resides.
-
Create an instance of a blittable type and show when it is copied.
- Can you prove that it was copied?
-
Create an instance of a non-blittable type and show when it is moved.
- Can you prove that it was moved?
- Can you show that the moved-from is invalid?
-
Repeat the second exercise but clone the non-blittable type instead
of moving it.
- Can you show that the cloner is still valid?
14. Solution for Exercise #1
Solution
Addresses are different, values are the same => copy. voila!