about
RustStory Data
6/8/2022

Chapter 2. - Rust Data

Types, type deduction, ownership

2.0  Prologue

Compiler enforced memory safety is one of the primary features of Rust. That is implemented with a strict ownership policy, ensuring that aliasing and mutation do not occur concurrently for any instance of a Rust type.
Definition: Alias Two or more identifiers are bound to part or all of the same memory location.
Example: let mut iden1 = vec![1,2,3]; let iden2 = &iden1[1];
Definition: Mutation Operations on an identifier change its bound value.
Example: iden1.push(4);
By value, we mean the state of an identifier's bound instance. The value of a String, for example, is the collection of all its characters. The issue for memory safety is that changes to the String's state may change its memory allocation if the original allocation did not have enough capacity for the change. Thus all aliases of the identifier responsible for the change will hold invalid references. When new names bind to an existing value, the value will, for blittable types, be copied, and will be moved for non-blittable types. Note that a move is a tranfer of ownership, so the original owner may no longer access that value. References support borrowing, providing access to the owning instance's value while suspending the owner's ability to mutate. When borrowing terminates, the owner's ability to mutate is restored. All of these rules are enforced by the Rust compiler. When a rule is violated, the compiler emits a useful error message that helps a designer fix the violation.

2.1  Data and its Life Cycle

Rust data comes in two flavors (see basic-types for details):
  1. Blittable types:
    Stored entirely in one contiguous block of stack memory.
    • Basic Types:
      u8, i8, u16, i16, u32, i32, u64, i64, usize, isize, f32, f64, bool, char, str
    • Aggregate Types:
      array, tuple, struct if all their items are blittable
  2. Non-Blittable types:
    Control block stored in one contiguous block of stack memory with references to data held in the heap.
    • Std Library Types:
      String, Box, Vec, VecDeque, LinkedList, HashMap, HashSet, BTreeMap, BTreeSet, BinaryHeap
    • Aggregate Types:
      array, tuple, struct if each has at least one non-blittable member
Figure 1. String Move
Blittable data values are copied:
let x = 3.5; Creates an x:f64 on the stack initialized with the value 3.5.
let y = x; Creates an y:f64 on the stack and copies x's value into y. x is still valid.
Non-Blittable data values are moved:
let s = String::from("a string"); Creates an s:String control block on the stack pointing to continguous heap memory containing the characters "a string"
let t = s; Copies the s:String control block to t (still pointing to s's characters) and marks s as moved.
When x and y go out of scope, that is, the thread of execution leaves the scope in which x and y are defined, nothing happens other than the stack frame is marked as free and may be over-written at any time due to another stack allocation. When s and t go out of scope, the string Drop trait method is called on t, deallocating its character memory in the heap. The Drop trait method is not called on s since it no longer owns anything on the heap.
Figure 1. String Clone
There is an alternative to that scenario. Many of the stdlibrary types, including String, implement the Clone trait, giving them a clone method. So, a designer can replace a move that invalidates its source with the construction of a clone, like this:
let s = String::from("a string"); Creates an s:String control block on the stack pointing to continguous heap memory containing the characters "a string"
let t = s.clone(); Creates a t:String control block on the stack pointing to continguous heap memory containing a copy of the characters "a string"
Now, both s and t are valid, each pointing to their own character allocations on the heap, which, immediately following the clone operation, have the same characters. When s and t go out of scope, the String Drop trait method drop() is called on both s and t because they each own unique character array allocations.

2.2  Rust Types and Type Deduction

Rust has a strong type inference engine so you usually don't need to qualify newly created instances with their types as long as they are initialized in the definition. The Rust let declarator works much like the C++ auto declarator. This is illustrated in the code example, below.
Type Deduction Fully qualified types vs. deduced types //mod types; use std::fmt::{Debug}; #[allow(dead_code)] pub fn run () { /*-- fully specified --*/ let i:i32 = 5; let f:f64 = 3.4; let a:[f32; 5] = [1.0, 1.5, 2.0, 1.5, 1.0]; let t:(i32, f64, String) = (1, 2.0, "three".to_string()); #[derive(Debug)] struct S{i:i32, s:&'static str, }; let s:S = S{i:15, s:"a literal string" }; #[derive(Debug)] enum E {BS(String), MS(String), PhD(String),}; let e:E = E::MS("Computer Engineering".to_string()); print!("\n -- fully specified types --\n"); print!("\n i = {:?}", i); print!("\n f = {:?}", f); print!("\n a = {:?}", a); print!("\n t = {:?}", t); print!("\n s = {:?}", s); print!("\n e = {:?}", e); /*-- using type deduction --*/ let i = 5; let f = 3.4; let a = [1.0, 1.5, 2.0, 1.5, 1.0]; let t = (1, 2.0, "three".to_string()); let s = S{i:15, s:"a literal string" }; let e = E::MS("Computer Engineering".to_string()); print!("\n\n -- using type deduction --\n"); print!("\n i = {:?}", i); print!("\n f = {:?}", f); print!("\n a = {:?}", a); print!("\n t = {:?}", t); print!("\n s = {:?}", s); print!("\n e = {:?}", e); } Output: C:\github\JimFawcett\RustBasicDemos\rust_probes> cargo -q run -- fully specified types -- i = 5 f = 3.4 a = [1.0, 1.5, 2.0, 1.5, 1.0] t = (1, 2.0, "three") s = S { i: 15, s: "a literal string" } e = MS("Computer Engineering") -- using type deduction -- i = 5 f = 3.4 a = [1.0, 1.5, 2.0, 1.5, 1.0] t = (1, 2.0, "three") s = S { i: 15, s: "a literal string" } e = MS("Computer Engineering")

2.2.1  Basic Data Types

Rust basic types are: i8, u8, i16, u16, i32, u32, i64, u64, i128, u128, isize, usize f32, f64, char, bool, () The last of these, "()" is the unit type. It represents the absence of a value. Examples of all the basic types including code and output.
Basic Types Basic Types code from main: title("exploring basic types".to_string()); /* Rust basic types: i8, u8, i16, u16, i32, u32, i64, u64, i128, u128, isize, usize f32, f64, char, bool, () */ let demo :i8 = 3; putln(&"let demo :i8 = 3;"); log(&demo); separator(); let demo = 5; putln(&"let demo = 5;"); log(&demo); separator(); let demo :usize = 7; putln(&"let demo :usize = 7;"); log(&demo); /* Rust floats: f32, f64 */ separator(); let demo = 3.5; putln(&"let demo = 3.5;"); log(&demo); separator(); let demo :f32 = -3.5; putln(&"let demo :f32 = -3.5;"); log(&demo); /* Rust chars: char */ separator(); let demo = 'a'; putln(&"let demo = 'a';"); log(&demo); separator(); let demo :char = 'Z'; putln(&"let demo :char = 'Z';"); log(&demo); /* Rust boolean: bool */ separator(); let demo = true; putln(&"let demo = true;"); log(&demo); separator(); let demo :bool = false; putln(&"let demo :bool = false"); log(&demo); /* Rust unit type: () */ separator(); let demo = (); putln(&"let demo = ();"); log(&demo); separator(); let demo :() = (); putln(&"let demo :() = ();"); log(&demo); main.rs - defines put, putln, and separator #[allow(unused_imports)] use display::{putline, title, show_type, log, putlinen}; use std::fmt::{Debug, Display}; use std::any::Any; #[allow(dead_code)] fn put<T: Any + Debug + Display>(value: &T) { print!("{}", value); } fn putln<T: Any + Debug + Display>(value: &T) { let mut str_temp = String::new(); str_temp.push_str("\n "); str_temp.push_str(&value.to_string()); print!("{}", str_temp); } fn separator() { put(&"\n ---------------------------------"); } fn main() { // code elided - see panel above } Output for Basic Types: C:\github\JimFawcett\RustBasicDemos\data_types> cargo -q run exploring basic types ----------------------- let demo :i8 = 3; TypeId: i8, size: 1 value: 3 --------------------------------- let demo = 5; TypeId: i32, size: 4 value: 5 --------------------------------- let demo :usize = 7; TypeId: usize, size: 4 value: 7 --------------------------------- let demo = 3.5; TypeId: f64, size: 8 value: 3.5 --------------------------------- let demo :f32 = -3.5; TypeId: f32, size: 4 value: -3.5 --------------------------------- let demo = 'a'; TypeId: char, size: 4 value: 'a' --------------------------------- let demo :char = 'Z'; TypeId: char, size: 4 value: 'Z' --------------------------------- let demo = true; TypeId: bool, size: 1 value: true --------------------------------- let demo :bool = false TypeId: bool, size: 1 value: false --------------------------------- let demo = (); TypeId: (), size: 0 value: () --------------------------------- let demo :() = (); TypeId: (), size: 0 value: ()
The basic types are blittable, and so implement the Copy trait.

2.2.2  Aggregate Data Types

Rust aggregate types are: arrays, tuples, strings, references, structs, and enums
References are borrows /* attempt to mutate after borrow */ let mut s = String::from("s is owner"); slog(&s); { let rs = &s; // borrow s // statement below fails to compile // owner can't mutate after borrow // s += " with stuff"; slog(&rs); } // borrow ends here s += " with stuff"; slog(&s);
Examples for all of the aggregate types, showing code and output from RustBasicDemos: data_types:
Aggregate Types Aggregate Types code from main // code elided /* Rust arrays: [1,2,3] */ separator(); let demo = [1, 2, 3]; putln(&"let demo = [1, 2, 3];"); log(&demo); separator(); let demo = [(1.5, 2.3), (3.1, 3.9)]; putln(&"let demo = [(1.5, 2.3), (3.1, 3.9)];"); log(&demo); /* Rust tuples: (1,'z',3.5) */ separator(); let demo = (1, 2.5, (1,'a'), [1,2,3]); putln(&"let demo = (1, 2.5, (1,'a'), [1,2,3]);"); log(&demo); /* Rust Strings: String::from("a string") */ separator(); let demo = String::from("a demo String"); putln( &"let demo = String::from(\"a demo String\");" ); log(&demo); /* Rust references: */ separator(); let mut demo = String::from("a demo String"); putln( &"let demo = String::from(\"a demo String\");" ); log(&demo); let rdemo = &mut demo; putln(&"let rdemo = &mut demo;"); putln(&"redemo.push_str(\" more stuff\")"); rdemo.push_str(" more stuff"); // compile fails - this is attemp to make second // mutable borrow because push_str takes &self // demo.push_str(" still more stuff"); log(rdemo); putln(&"demo.push_str(\" still more stuff\""); // this call succeeds only because rdemo // is not used below call demo.push_str(" still more stuff"); log(&demo); // log(rdemo); // if this call is uncommented // both calls above fail putline(); /* attempt to mutate after borrow */ let mut s = String::from("s is owner"); slog(&s); { let rs = &s; // borrow s // statement below fails to compile // owner can't mutate after borrow // s += " with stuff"; slog(&rs); } // borrow ends here s += " with stuff"; slog(&s); putline(); /*------------------------------------------- Rust structs: struct Point { x:f64, y:f64, z:f64, t:} */ separator(); #[derive(Debug)] struct Point { x:f64, y:f64, z:f64, name:String, } let demo = Point { x:1.5, y:2.5, z:3.7, name:String::from("Peter") }; putln( &"struct Point { x:f64, y:f64, z:f64, name:String, }" ); putln( &"let demo = Point { x:1.5, y:2.5, z:3.7, name:String::from(\"Peter\") };" ); log(&demo); /* Rust enums: enum POS { bs, ms, phd } */ separator(); #[derive(Debug)] #[allow(dead_code)] enum POS { BS(String), MS(String), PhD(String), }; let demo = POS::MS( String::from("Computer Engineering") ); putln( &"enum POS { BS(String), MS(String), PhD(String), };" ); putln( &"let demo = POS::MS( String::from(\"Computer Engineering\") );" ); log(&demo); // code elided Output for Aggegate Types C:\github\JimFawcett\RustBasicDemos\data_types> cargo -q run exploring basic types ----------------------- let demo :i8 = 3; TypeId: i8, size: 1 value: 3 --------------------------------- let demo = 5; TypeId: i32, size: 4 value: 5 --------------------------------- let demo :usize = 7; TypeId: usize, size: 4 value: 7 --------------------------------- let demo = 3.5; TypeId: f64, size: 8 value: 3.5 --------------------------------- let demo :f32 = -3.5; TypeId: f32, size: 4 value: -3.5 --------------------------------- let demo = 'a'; TypeId: char, size: 4 value: 'a' --------------------------------- let demo :char = 'Z'; TypeId: char, size: 4 value: 'Z' --------------------------------- let demo = true; TypeId: bool, size: 1 value: true --------------------------------- let demo :bool = false TypeId: bool, size: 1 value: false --------------------------------- let demo = (); TypeId: (), size: 0 value: () --------------------------------- let demo :() = (); TypeId: (), size: 0 value: () --------------------------------- let demo = [1, 2, 3]; TypeId: [i32; 3], size: 12 value: [1, 2, 3] --------------------------------- let demo = [(1.5, 2.3), (3.1, 3.9)]; TypeId: [(f64, f64); 2], size: 32 value: [(1.5, 2.3), (3.1, 3.9)] --------------------------------- let demo = (1, 2.5, (1,'a'), [1,2,3]); TypeId: (i32, f64, (i32, char), [i32; 3]), size: 32 value: (1, 2.5, (1, 'a'), [1, 2, 3]) --------------------------------- let demo = String::from("a demo String"); TypeId: alloc::string::String, size: 12 value: String (13): a demo String --------------------------------- let demo = String::from("a demo String"); TypeId: alloc::string::String, size: 12 value: String (13): a demo String let rdemo = &mut demo; redemo.push_str(" more stuff") TypeId: alloc::string::String, size: 12 value: String (24): a demo String more stuff demo.push_str(" still more stuff" TypeId: alloc::string::String, size: 12 value: String (41): a demo String more stuff still more stuff TypeId: alloc::string::String, size: 12 value: "s is owner" TypeId: &alloc::string::String, size: 4 value: "s is owner" TypeId: alloc::string::String, size: 12 value: "s is owner with stuff" --------------------------------- struct Point { x:f64, y:f64, z:f64, name:String, } let demo = Point { x:1.5, y:2.5, z:3.7, name:String::from("Peter") }; TypeId: data_types::main::Point, size: 40 value: Point { x: 1.5, y: 2.5, z: 3.7, name: "Peter" } --------------------------------- enum POS { BS(String), MS(String), PhD(String), }; let demo = POS::MS(String::from( "Computer Engineering") ); TypeId: data_types::main::POS, size: 16 value: MS("Computer Engineering")
Aggregate types are blittable if and only if they have all blittable members, e.g., no Strings, Vecs, ... In that case they can acquire the Copy trait, simply by qualifying them as implementing derived Copy: #[derive(Debug, Copy, Clone)] struct my_struct { ... } For this declaration the compiler generates these traits.
  • Debug allows you to use {:?} in a format which uses a standard formatting process for each of the Rust types.
  • Copy causes the compiler to copy an instance's value by blitting (memcpy) to the new location. The compiler will refuse to derive Copy if any member is non-blittable or the type already implements the Drop trait.
  • Clone is not called implicitly, but a designer can write code that calls clone() and then pays whatever performance penalty accrues for making the copy. If you implement Copy you are also required to implement Clone.
If an aggregate type is non-blittable, then attempting to derive the Copy trait is a compile error. However, you can implement Clone using the clone method on any non-blittable member if the member has the Clone trait.

2.2.3  Slices of Aggregate Types:

A slice is a non-owning view into an aggregate data structure that may or may not be viewing the complete data structure. Consider an array: let arr = [1, 2, 3, 4, 5, 6];
  • let slc1 = &arr[..]; // view the entire array
  • let slc2 = &arr[0..6]; // same as slc1
  • let slc3 = &arr[..3]; // views elements [1, 2, 3]
  • let slc4 = &arr[1..]; // views elements [2, 3, 4, 5, 6]
  • let slc5 = &arr[1..4]; // views elements [2, 3, 4]
It only makes sense to take slices of array-like things, e.g., arrays, vectors, and strings. Also, string slices only make sense if all the string characters are ASCII. We will discuss this further in the next section.

2.2.4  String Types:

Rust provides two native string types: String and str, and two types intended for use with C language bindings: OsString and CString. We will focus here on String and str. The str type is part of the core Rust language and String is provided in the std library. Both contain sequences of utf-8 characters. The size of utf-8 characters ranges from 1 to 4 bytes. But String is implemented using Vec<u8>, so indexing into the Vec only yields a byte which will be a whole character if ASCII, but only part of a character otherwise. That means that you can't index Strings.

2.2.4.1  String

You retrieve the ith utf-8 char from a String, s, using: s.chars().nth(i).unwrap(). chars() is a String iterator that knows how to find utf-8 character boundaries. nth(i) calls next on the iterator i times. That returns a std::option that contains either Some(ch) or None. If the indexing succeeded that returns Some(ch) and we can use ch directly. Note that this is an order N process because we walk down the string looking for character boundaries. Using unwrap() attempts to use the character directly. If indexing failed that would result in a panic. In cases where a panic is not appropriate (flight navigation system for the Boeing 797) we use matching to react to the option. We will discuss option processing in the next chapter, Operations. The method described above works for all utf-8 character sets. However, for languages that use diacritics, the diacritics get encoded in a separate char even though a speaker of the language would say that those are part of an adjacent character in that language (Hindi for example). the chars() iterator is not smart enough to handle that situation. Here's a reference: ch08-02-strings in the Rust Book Note that Strings are not blittable, so rebinding a string to a new name transfers ownership, and taking a borrow reference suspends the owner's ability to mutate until the borrow ends.

2.2.4.2  str

The str type represents litteral strings like "a literal string". These are implemented with contiguous blocks of memory, often on the stack, and so are blittable. You almost always encounter literal strings as references, &s. strs can be converted to String instances in several ways. Here's two:
  1. let s = String::from("a literal string");
  2. let s = "a literal string".to_string();
And we can create an str by taking a slice of a String or a literal string:
  1. let s1 = "Hello world"; // slice of the whole literal
  2. let s2 = &s[1..3]; // second through 4th bytes of s
Both s1 and s2 have type &str, a reference to a literal string.
Taking a complete slice always works, but, since Rust chars are utf-8 with sizes that range from 1 byte (ASCII characters) to 4 bytes for math symbols and emojis, a partial slice like s2 may not have a correct representation of the second through 4th characters of s. An excellent discussion of utf-8 strings is provided by amos.

2.2.5  String Examples:

The String type has methods:
  • let s = String::new();
    Creates new empty String instance
  • let s = String::from("a literal");
    Creates instance from literal
  • let t = s.replace("abc","xyz");
    t is a copy of s with every instance of "abc" replaced with "xyz"
  • s.len();
    returns length of s in bytes, not chars
  • let slice = s.as_str();
    returns slice of entire String s contents
  • s.push('a');
    append char 'a' to end of s.
  • s.push_str("abc");
    appends "abc" to the end of s
  • let st = s.trim();
    returns string with leading and trailing whitespace removed.
  • let iter = s.split_whitespace();
    returns iterator over whitespace separated tokens
  • let iter = s.split('\n');
    returns iterator over lines
  • let iter = s.chars();
    returns an iterator over the utf-8 chars of s
Here's an example showing many of these methods in action.
String Examples: string-probes::main.rs #[allow(unused_imports)] use display::{ log, slog, show, show_type, show_value, putline, putlinen, main_title, sub_title }; /*----------------------------------------------------------- Note: Strings hold utf8 characters, which vary in size, so you you can't directly index String instances. */ #[allow(dead_code)] pub fn at(s:&String, i:usize) -> char { s.chars().nth(i).unwrap() } /*----------------------------------------------------------- note: - order n, as str chars are utf8, e.g., from 1 to 5 bytes -----------------------------------------------------------*/ fn main() { main_title("string_probes"); putline(); /*-- char --*/ let v:Vec<char> = vec!['R', 'u', 's', 't']; log(&v); log(&'R'); putline(); let ch:u8 = 'a' as u8; log(&ch); show("char is ", &(ch as char)); putline(); /*-- String --*/ let s:String = String::from("Rust"); log(&s); let i:usize = 2; let ch = at(&s, i); print!("\n in string \"{}\", char at {} is {}", &s, i, ch); show("length in bytes of s = {:?}", &s.len()); putline(); let s1 = s.clone(); let v:Vec<u8> = Vec::from(s1); slog(&v[0]); show("vec from string",&v); putline(); /*-- str --*/ let s_slice = &s[..]; // slice containing all chars of s slog(&s_slice); show("s_slice = ", &s_slice); putline(); let s_slice2 = s.as_str(); slog(&s_slice2); putline(); /*-- create string and mutate --*/ let mut s = String::new(); s.push('a'); s.push(' '); s.push_str("test string"); log(&s); putline(); let t = s.replace("string","Rust String"); slog(&t); putline(); for tok in s.split_whitespace() { print!("\n {}", tok); } putline(); //----------------------- // this works too // let iter = s.split_whitespace(); // for tok in iter { // print!("\n {}", tok); // } // putline(); /*----------------------------------------------------- Another, order n, way to index string: - chars returns iterator over utf8 chars in string slice - nth(i) calls next on iterator until it gets to i - nth(i) returns std::option::Option<char>: - that contains Some(ch) or None if operation failed */ let result = s.chars().nth(0); match result { Some(r) => show("s.chars().nth(0) = ", &r), None => print!("\n couldn't extract char"), } show("s = ", &s); let result = s.chars().nth(2); match result { Some(r) => show("s.chars().nth(2) = ", &r), None => print!("\n couldn't extract char"), } show("s = ", &s); putline(); { /*------------------------------------------------- Caution here: - slice is returning array of bytes, not utf8 chars - this works only because we use all ASCII chars */ /*-- slices are non-owning views and are borrows of s --*/ let slice_all = &s; slog(&slice_all); show("slice_all = ", &slice_all); putline(); let third = &s[2..3]; // string slice with one char slog(&third); show("third = ",&third); putline(); /*-- this works for utf-8 encoding --*/ let ch = third.chars().nth(0); // log(&ch); match ch { Some(x) => { log(&x); show("match ch = ", &x); }, None => print!("\n can't return ch"), } /////////////////////////////////////////////////// // compile fails // - can't modify owner while borrows are active //------------------------------------------------ // s.push('Z'); // slog(&slice_all); } // borrow ends here s.push('Z'); // ok, borrow no longer active putline(); sub_title("That's all Folks!"); putlinen(2); } Output C:\github\JimFawcett\RustBasicDemos\string_probes> cargo -q run string_probes =============== TypeId: alloc::vec::Vec , size: 12 value: ['R', 'u', 's', 't'] TypeId: char, size: 4 value: 'R' TypeId: u8, size: 1 value: 97 char is 'a' TypeId: alloc::string::String, size: 12 value: String (4): Rust in string "Rust", char at 2 is s length in bytes of s = {:?}4 TypeId: u8, size: 1 value: 82 vec from string[82, 117, 115, 116] TypeId: &str, size: 8 value: "Rust" s_slice = "Rust" TypeId: &str, size: 8 value: "Rust" TypeId: alloc::string::String, size: 12 value: String (13): a test string TypeId: alloc::string::String, size: 12 value: "a test Rust String" a test string s.chars().nth(0) = 'a' s = "a test string" s.chars().nth(2) = 't' s = "a test string" TypeId: &alloc::string::String, size: 4 value: "a test string" slice_all = "a test string" TypeId: &str, size: 8 value: "t" third = "t" TypeId: core::option::Option , size: 4 value: Some('t') TypeId: char, size: 4 value: 't' match ch = 't' That's all Folks! -------------------
The fact that Rust Strings hold utf-8 characters is good news and bad news. The good news is they can represent virtually anything a console can emit, e.g., ASCII chars, math symbols, arabic fonts, european diacritics, and emojis. The bad news is that you can't index a Rust String in constant time; and converting to other data structures can get messy.

2.3  Structs

In Rust, most structs are aggregates of one or more fields where the fields may be arbitray types, named types, or unit type:
  1. StructExprStruct: struct Person { name:String, occupation:String, age:u32, }
  2. StructExprTuple: struct Person { String, String, u32, }
  3. StructExprUnit: struct Person;
Here are some examples:
Struct Code: #[allow(unused_imports)] use display::{*}; use std::fmt; /*-- ExprStruct struct --*/ #[derive(Debug)] struct Person1 { name:String, occup:String, id:u32, } #[allow(dead_code)] impl Person1 { fn show(&self) { print!("\n Person1: {:?}", &self); } } /*-- ExprTuple struct --*/ #[derive(Debug)] struct Person2 ( String, String, u32 ); #[allow(dead_code)] impl Person2 { fn show(&self) { print!("\n Person2: {:?}", &self); } } /*-- ExprUnit struct --*/ #[derive(Debug)] struct Person3; #[allow(dead_code)] impl Person3 { fn show(&self) { print!("\n Person3"); } } Using Code: sub_title("Demonstrating Basic Structs"); let p1 = Person1 { name:"Jim".to_string(), occup:"dev".to_string(), id:42 }; p1.show(); let p2 = Person2 { 0:"Jim".to_string(), 1:"dev".to_string(), 2:42 }; p2.show(); let p3 = Person3; p3.show(); putline(); Output: Demonstrating Basic Structs ----------------------------- Person1: Person1 { name: "Jim", occup: "dev", id: 42 } Person2: Person2("Jim", "dev", 42) Person3
You may be puzzled by the "Impl" method implementations. They add methods to a struct that interact with the struct's fields. That creates a component type. When defining components we usually make the struct public, its fields private, and at least some of its methods public. We will discuss all this in the next two chapters.

2.4  Enumerations

An enumeration is a type identifier with a set of enumeration item fields. Fields may be:
  • ItemDiscriminant: a named integral value enum Names { John, Sally = 35, Roger };
  • ItemTuple: a named tuple with items specified by type enum Names { Alok(String, f64), Priya(String, f64), Ram(String, f64) };
  • ItemStruct: a named struct with items specified by name and type enum Names { Jun { occupation: String, age: f64 }, Xing { occupation: String, age: f64 }, Shi { occupation: String, age: f64 }, }
Enumerations can be generic. A common example in Rust code is the Option: enum Option<T>{ Some(T), None } which can be used as the return value of a function that may or may not generate a result. Here are some examples:
Three types of enumerations /*-- discriminant enum --*/ enum Name { John, Jim=42, Jack } /*-- tuple enum --*/ enum NameTuple { John(String, u32), Jim(String, u32), Jack(String, u32) } /*-- struct enum --*/ enum NameStruct { John { occup:String, id:u32 }, Jim { occup:String, id:u32 }, Jack { occup:String, id:u32 } }

2.5  Type Aliases

Type aliases provide an alternate name for an existing type, but is in fact the same type. You construct an alias like this:
  • type PointF = (f64, f64, f64); // tuple of three doubles
  • type VecPoint = Vec<PointF>
Aliases help us provide meaningful application domain names for standard types and shortcuts for long type names. Note that the Rust naming convention uses snake_case for functions and CamelCase for types.

2.6  Std Lib Data Types

The std collections types are: Vec, VecDeque, LinkedList, HashMap, HashSet, BTreeMap, BTreeSet, BinaryHeap There are many other types defined in the stdlib for fs (FileSystem), io, net (TCP, UDP types), process, thread, time, ... In this section we will only look briefly at Vec and HashMap.
StdLib Data Types Stdlib Types code from main /* Rust Vectors: Vec<(i32, f64, char)> - vector of tuples */ separator(); let mut demo :Vec<(i32, f64, char)> = Vec::new(); demo.push((1, 2.5, 'z')); demo.push((2, 3.5, 'A')); putln(&"let mut demo :Vec<(i32, f64, char)> = Vec::new();"); log(&demo); /* Rust HashMap: HashMap<String,i32> */ separator(); use std::collections::HashMap; let mut demo :HashMap<String, i32> = HashMap::new(); demo.insert("one".to_string(), 1); demo.insert("two".to_string(), 2); putln(&"let mut demo :HashMap<String, i32> = HashMap::new();"); log(&demo); Output for Stdlib Types --------------------------------- let mut demo :Vec<(i32, f64, char)> = Vec::new(); TypeId: alloc::vec::Vec<(i32, f64, char)>, size: 12 value: [(1, 2.5, 'z'), (2, 3.5, 'A')] --------------------------------- let mut demo :HashMap = HashMap::new(); TypeId: std::collections::hash::map::HashMap, size: 40 value: {"one": 1, "two": 2}
The std collections types are all non-blittable and are moved not copied. All implement the Clone trait.

2.7  Epilogue:

This chapter has been all about storing and presenting data in scalar, aggregate, and structured forms. In the next chapter we will be looking at ways to operate on this data with functions, operators, and lambdas.

2.7.1  Exercises:

  1. Construct a vec of i32 elements, populate it with 5 arbitrary elements, then display the value and address of each element. This reference may help. Note that you don't need an unsafe block for this exercise.
  2. Create an instance of std::collections::HashMap. Populate it with information about projects on which you are working. Use project name as the key, and provide a small collection of items about the project, i.e., purpose, programming language, and status. Display the results on the console.
  3. Create an array of Strings, populating with arbitrary values. Convert the array to a Vec.
  4. Construct a str instance and convert it to a String. Evaluate the address of the str, the String, and the first element of the String. Now, convert the String back to another str. Display everything you have built and evaluated.
  5. Declare a struct that has fields to describe your current employment. Display that on the console.
  6. Repeat the last exercise, but use a tuple. Use type aliases to make the tuple understandable.

2.7.2  References:

Reference Link Description
Character sets The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Escuses!) [author's title] - Joel Spolsky
utf-8 Strings - amos Illustrating how utf-8 strings work with C and with Rust code.
Rust Strings Rust Strings are implemented with Vec<u8> but interpreted as utf-8 chars
regex Crate Rust regex crate provides facilities for parsing, compiling, and executing regular expressions.
Rust Lifetimes Very clear presentation of borrow lifetimes.
Rust Reference: Structs Rust Reference is the official language definition - surprisingly readable.
Rust Containers Container diagrams
rust-lang.org home page Links to download and documentation
Tutorial - tutorialspoint.com Tutorials for most of the Rust parts with code examples.
  Next Prev Pages Sections About Keys