To have the best experience of following this tutorial, I would highly recommend checking out the following note, which provides an interactive coding experience!
fntakes_u32(x:u32){println!("u32: {x}");}fntakes_i8(y:i8){println!("i8: {y}");}fnmain(){let x =10;let y =20;takes_u32(x);takes_i8(y);// takes_u32(y);}
๐
The compiler infers the type of the variable from the value assigned to it.
Such declaration (let x = 10;) is identical to the explicit declaration of a type (let x : u32= 10;), instead of dynamic โany typeโ
Function
fnfib(n:u32)->u32{if n <=2{// The base case.// unimplemented!("Implement this");return1;}else{// The recursive case.// todo!("Implement this")returnfib(n -1)+fib(n -2);}}fnmain(){let n =20;println!("fib(n) = {}",fib(n));}
๐ก
use macros of unimplemented! and todo! to indicates unfinished code.
The difference between them is, todo! conveys an intent of implementing the functionality later and the message is "not yet implemented", while unimplemented! makes no such claims, and its message is merely โnot implementedโ
Control Flow
Loops
fnmain(){letmut x =200;while x >=10{
x = x /2;}println!("Final x: {x}");}
while
fnmain(){for x in1..5{println!("x: {x}");}}
for
๐
1..5 is a range that yields 1, 2, 3, 4
use 1..=5 syntax for an inclusive range
fnmain(){letmut i =0;loop{
i +=1;println!("{i}");if i >100{break;}}}
loop
โ ๏ธ
The loop statement just loops forever, until a break
break and continue
fnmain(){'outer:for x in1..5{println!("x: {x}");letmut i =0;let result ='inner:loop{println!("x: {x}, i: {i}");
i +=1;if i >= x {break'inner i;}if i ==3{break'outer;}};println!("result: {result}");}}
๐
Both continue and break can optionally take a label argument, such as 'inner and 'outer here, which is used to break out of nested loops
๐ก
Note that loop is the only looping construct which returns a non-trivial value. This is because itโs guaranteed to be entered at least once (unlike while and for loops).
Blocks and Scopes
fnmain(){let a =10;println!("before: {a}");{let a ="hello";println!("inner scope: {a}");let a =true;println!("shadowed in inner scope: {a}");}println!("after: {a}");}
๐
A variableโs scope is limited to the enclosing block.
Re-declare a variable with an existing name is called โshadowingโ, which is different from mutation because more memory is taken.
๐ก
Shadowing is convenient for holding on to values after .unwrap().
fnmain(){let z =13;let x ={let y =10;println!("y: {y}");
z - y
};println!("x: {x}");}
๐
block has a value and a type, which are those of the last expression of the block
If the last expression ends with ;, then the resulting value and type is (), i.e., the โunit typeโ.
Functions
fngcd(a:u32, b:u32)->u32{if b >0{gcd(b, a % b)}else{
a // identical to "return a;"}}fnmain(){println!("gcd: {}",gcd(143,52));}
๐
Declaration parameters are followed by a type, then a return type.
โ ๏ธ
It is similar to type hint in Python (after version 3.5), but itโs a must in Rust.
๐ก
Functions have no return value will return the โunit typeโ, (). The compiler will infer this if the return type is omitted.
โ ๏ธ
Overloading is not supported โ each function has a single implementation.
โ ๏ธ
Rust functions always take a fixed number of parameters. (Macros are sort of special cases)
Default arguments are not supported.
Tuples and Arrays
fnmain(){// a is an arrayletmut a:[i8;10]=[42;10];
a[5]=0;println!("a: {a:?}");}
๐
A value of the array type [T; N] holds N (a compile-time constant) elements of the same type T.
โ ๏ธ
Note that the length of the array is part of its type, which means that [u8; 3] and [u8; 4] are considered two different types.
โ ๏ธ
The {} gives the default output in println! macro, while {:?} gives the debug output. Types such as integers and strings implement the default output, but arrays only implement the debug output. This means that we must use debug output here.
๐ก
Adding #, e.g., {a:#?}, invokes a โpretty printingโ format
fnmain(){// t is a tuplelet t:(i8,bool)=(7,true);println!("t.0: {}", t.0);println!("t.1: {}", t.1);}
๐
Tuples have a fixed length and group together values of different types into a compound type.
The empty tuple () is also known as the โunit typeโ. It is both a type, and the only valid value of that type. Analogous to void in C++
Array Iteration
fnmain(){let primes =[2,3,5,7,11,13,17,19];for prime in primes {for i in2..prime {assert_ne!(prime % i,0);}}}
๐ก
The for ... in ... syntax actually uses the IntoIterator trait
๐ก
Some macros have debug-only variants like debug_assert_ne!, which compile to nothing in release builds.
The _ pattern is a wildcard pattern which matches any value.
โ ๏ธ
The expressions must be irrefutable, meaning that it covers every possibility, so _ is often used as the final catch-all case.
Destructuring
fnmain(){describe_point((1,0));}fndescribe_point(point:(i32,i32)){match point {(0, _)=>println!("on Y axis"),(_,0)=>println!("on X axis"),(x, _)if x <0=>println!("left of Y axis"),(_, y)if y <0=>println!("below X axis"),
_ =>println!("first quadrant"),}}
#[rustfmt::skip]fnmain(){let triple =[0,-2,3,4,5];println!("Tell me about {triple:?}");match triple {// [0, y, z] => println!("First is 0, y = {y}, and z = {z}"),[1,..]=>println!("First is 1 and the rest were ignored"),[_,..,4]=>println!("First is ignored, the last is 4"),[a@.., b]=>println!("the last is {b}, a = {a:?}"),[.., b]=>println!("the last is {b}, whatever the previous elements are"),
_ =>println!("All elements were ignored"),}}
๐
.. will expand to account for different number of elements.
With patterns [a@.., b], all elements except for the last one will be matched into a list called a
References
Shared References
fnmain(){let a ='A';let b ='B';letmut r:&char=&a;println!("r: {}",*r);
r =&b;let s =&b;println!("r: {}",*r);println!("s: {}",*r);}
๐
A shared reference to a type T has type &T. A reference value is made with the & operator. The * operator โdereferencesโ a reference, yielding its value.
โ ๏ธ
In this example, r is mutable so that it can be reassigned (r = &b). Note that this re-binds r, so that it refers to something else. This is different from C++, where assignment to a reference changes the referenced value.
๐ก
Note Shared References here is relative to Exclusive references.
The reason why the syntax (let mut a = &x;) is called a shared reference is because there could be other references sharing the accessibility of x, even if a is already there.
Exclusive references
fnmain(){letmut point =(1,2);let x_coord_ref =&mut point.0;let x_coord = point.0;// !panic here!: use of borrowed `point.0`// cannot borrow `point.0` as immutable because it is also borrowed as mutableletmut x_coord_ref_2 =&point.0;// !panic here!: immutable borrow occurs here// cannot borrow `point.0` as immutable because it is also borrowed as mutablelet x_coord_ref_3 =&mut point.0;// !panic here!: second mutable borrow occurs here// cannot borrow `point.0` as mutable more than once at a timelet y_coord_ref =&point.1;// this is ok*x_coord_ref =20;println!("point: {point:?}");}
๐
โExclusiveโ means that no other references (shared or exclusive) nor variables can access the borrowed value while the exclusive reference exists.
โ ๏ธ
Note the difference between let mut x: &i32 and let x: &mut i32.
ref keyword
The ref keyword in Rust is used in pattern matching to create a reference to a value.
#[derive(Debug)]structPerson{
name:&'staticstr,
age:u8,}fnmain(){let some_person =Some(Person{
name:"Alice",
age:25,});match some_person {Some(ref x)=>println!("Got a reference to: {:?}", x),None=>println!("Got nothing"),}println!("some_person: {:?}", some_person);}
๐ก
In the example above, we can still use some_person after match because we borrowed the value of x by reference. Run another example but without ref to see what will happen:
#[derive(Debug)]structPerson{
name:&'staticstr,
age:u8,}fnmain(){let some_person =Some(Person{
name:"Alice",
age:25,});match some_person {Some(x)=>println!("Got a reference to: {:?}", x),None=>println!("Got nothing"),}println!("some_person: {:?}", some_person);}
๐ก
However, if the value is of copyable type, we can still use the value after match even if there is no ref , as shown below
#[derive(Debug, Clone, Copy)]structPersonCopyable{
name:&'staticstr,
age:u8,}fnmain(){let some_copyable_person =Some(PersonCopyable{
name:"Alice",
age:25,});match some_copyable_person {Some(x)=>println!("Got a reference to: {:?}", x),None=>println!("Got nothing"),}println!("some_copyable_person: {:?}", some_copyable_person);}
as_ref and as_mut
let text:Option<String>=Some("Hello, world!".to_string());let text_length:Option<usize>= text
.as_ref()// First, cast `Option<String>` to `Option<&String>` with `as_ref`,.map(|s| s.len());// then consume *that* with `map`, leaving `text` on the stack.println!("still can print text: {text:?}");
letmut x =Some(2);match x.as_mut(){Some(v)=>*v =42,None=>{},}assert_eq!(x,Some(42));
User-defined Types
Structs
structPerson{
name:String,
age:u8,}fndescribe(person:&Person){println!("{} is {} years old", person.name, person.age);}fnmain(){letmut peter =Person{
name:String::from("Peter"),
age:27,};describe(&peter);
peter.age =28;describe(&peter);let name =String::from("Avery");let age =39;let avery =Person{ name, age };describe(&avery);let jackie =Person{
name:String::from("Jackie"),..avery
};describe(&jackie);}
Named Structs
๐
The struct update syntax.. allows us to explicitly copy fields from another struct.
Note that this syntax must always be the last element.
โ ๏ธ
Unlike in C++, there is no inheritance between structs.
structPoint(i32,i32);structPoundsOfForce(f64);structNewtons(f64);fncompute_thruster_force()->PoundsOfForce{todo!("Ask a rocket scientist at NASA")}fnset_thruster_force(force:Newtons){// ...}fnmain(){let p =Point(17,23);println!("({}, {})", p.0, p.1);let force =compute_thruster_force();set_thruster_force(force);}
Tuple Structs
๐ก
Newtypes are a great way to encode additional information about the value in a primitive type, such as a number measured in some units
Enum
#[derive(Debug)]enumDirection{Left,Right,}#[derive(Debug)]enumPlayerMove{Pass,// Simple variantRun(Direction),// Tuple variantTeleport{ x:u32, y:u32},// Struct variant}fnmain(){letmut m =PlayerMove::Run(Direction::Left);println!("On this turn: {:?}", m);
m =PlayerMove::Teleport{ x:1, y:2};println!("On this turn: {:?}", m);}
๐
The enum keyword allows the creation of a type which has a few different variants
๐ก
Note these variants can have different primitive types, although all of these variants are of the same Enum type
#[repr(u32)]#[derive(Debug)]enumBar{A,// 0B=10000,C,// 10001}fnmain(){let a =Bar::A;println!("a: {:?}", a);println!("a(u32): {:?}", a asu32);let b =Bar::B;println!("b: {:?}", b asu16);println!("C(u32): {}",Bar::Casu32);println!("C(u8): {}",Bar::Casu8);}
๐ก
Rust uses minimal space to store the discriminant, unless specified
constants declare constant values. These represent a value, not a memory address. This is the most common thing one would reach for and would replace static as we know it today in almost all cases.
statics declare global variables. These represent a memory address. They would be rarely used: the primary use cases are global locks, global atomic counters, and interfacing with legacy C libraries.
Type Aliases
fnmain(){typeName=String;typeAge=u32;typePerson=(Name,Age);fnnew_person(name:Name, age:Age)->Person{(name, age)}let john =new_person(String::from("John"),42);println!("john: {:?}", john);}
๐
A type alias creates a name for another type.
โ ๏ธ
Note the difference between a tuple struct and a tuple type aliasPerson as a tuple struct
fnmain(){typeName=String;typeAge=u32;#[derive(Debug)]structPerson(Name,Age);fnnew_person(name:Name, age:Age)->Person{Person(name, age)}let john =new_person(String::from("John"),42);println!("john: {:?}", john);}
Pattern matching
Destructuring Structs and Enums
structFoo{
x:(u32,u32),
y:u32,}#[test]fndestruct_struct(){let foo =Foo{ x:(2,2), y:2};match foo {Foo{ x:(1, b), y }=>println!("x.0 = 1, b = {b}, y = {y}"),Foo{ y:2, x: i }=>println!("y = 2, x = {i:?}"),Foo{ y,..}=>println!("y = {y}, other fields were ignored"),}}
struct
enumResult{Ok(i32),Err(String),}fndivide_in_two(n:i32)->Result{if n %2==0{Result::Ok(n /2)}else{Result::Err(format!("cannot divide {n} into two equal parts"))}}#[test]fndestruct_enum(){let n =99;matchdivide_in_two(n){Result::Ok(half)=>println!("{n} divided in two is {half}"),Result::Err(msg)=>println!("sorry, an error happened: {msg}"),}}
enum
Let Control Flow
fnsleep_for(secs:f32){let dur =ifletOk(dur_)=std::time::Duration::try_from_secs_f32(secs){
dur_
}else{std::time::Duration::from_millis(500)};std::thread::sleep(dur);println!("slept for {:?}", dur);}#[test]fnif_let(){sleep_for(-10.0);sleep_for(0.8);sleep_for(0.5);}
if-let
๐ก
To be clear, in this case the if-let expression tries to match the value returned from std::time::Duration::try_from_secs_f32 to the pattern Ok(dur_)
#[test]fnwhile_let(){letmut name =String::from("Comprehensive Rust ๐ฆ");whileletSome(c)= name.pop(){println!("character: {c}");}}
while-let
Methods and Traits
Methods
#[derive(Debug)]structRace{
name:String,
laps:Vec<i32>,}implRace{// No receiver, a static methodfnnew(name:&str)->Self{Self{ name:String::from(name), laps:Vec::new()}}// Exclusive borrowed read-write access to selffnadd_lap(&mutself, lap:i32){self.laps.push(lap);}// Shared and read-only borrowed access to selffnprint_laps(&self, index:bool){println!("Recorded {} laps for {}:",self.laps.len(),self.name);if index {for(idx, lap)inself.laps.iter().enumerate(){println!("Lap {idx}: {lap} sec");}}else{for lap inself.laps.iter(){println!("{lap} sec");}}}// Exclusive ownership of selffnfinish(self){let total:i32=self.laps.iter().sum();println!("Race {} is finished, total lap time: {}",self.name, total);}}letmut race =Race::new("Monaco Grand Prix");
race.add_lap(70);
race.add_lap(68);
race.print_laps(false);
race.add_lap(71);
race.print_laps(true);
race.finish();// race.finish(); // You cannot call finish twice
๐
The self arguments specify the โreceiverโ - the object the method acts on. There are several common receivers for a method:
&self: borrows the object from the caller using a shared and immutable reference. The object can be used again afterwards.
&mut self: borrows the object from the caller using a unique and mutable reference. The object can be used again afterwards.
self: takes ownership of the object and moves it away from the caller. The method becomes the owner of the object. The object will be dropped (deallocated) when the method returns, unless its ownership is explicitly transmitted. Complete ownership does not automatically mean mutability.
mut self: same as above, but the method can mutate the object.
No receiver: this becomes a static method on the struct. Typically used to create constructors which are called new by convention.
๐ก
Note that Self is actually a type alias for the type the impl block is in (in this case, Race) and can be used elsewhere in the block. And the self in previous codes is an abbreviated term for self: Self.
Traits
structDog{
name:String,
age:i8,}structCat{
lives:i8,}traitPet{fntalk(&self)->String;fngreet(&self){println!("Oh you're a cutie! What's your name? {}",self.talk());}}implPetforDog{fntalk(&self)->String{format!("Woof, my name is {}!",self.name)}}implPetforCat{fntalk(&self)->String{String::from("Miau!")}}let captain_floof =Cat{ lives:9};let fido =Dog{ name:String::from("Fido"), age:5};
captain_floof.greet();
fido.greet();
Deriving
#[derive(Debug, Clone, Default)]structPlayer{
name:String,
strength:u8,
hit_points:u8,}let p1 =Player::default();// Default trait adds `default` constructor.letmut p2 = p1.clone();// Clone trait adds `clone` method.
p2.name =String::from("EldurScrollz");// Debug trait adds support for printing with `{:?}`.println!("{:?} vs. {:?}", p1, p2);
Trait Objects
structDog{
name:String,
age:i8,}structCat{
lives:i8,}traitPet{fntalk(&self)->String;}implPetforDog{fntalk(&self)->String{format!("Woof, my name is {}!",self.name)}}implPetforCat{fntalk(&self)->String{String::from("Miau!")}}let pets:Vec<Box<dynPet>>=vec![Box::new(Cat{ lives:9}),Box::new(Dog{ name:String::from("Fido"), age:5}),];for pet in pets {println!("Hello, who are you? {}", pet.talk());}
๐ก
Types that implement a given trait may be of different sizes. This makes it impossible to have things like Vec<dyn Pet> in the example above. See memory layout after allocating pets
๐
dyn Pet is a way to tell the compiler about a dynamically sized type that implements Pet.
Run following codes for verification:
/// Pick `even` or `odd` depending on the value of `n`.fnpick<T>(n:i32, even:T, odd:T)->T{if n %2==0{
even
}else{
odd
}}println!("picked a number: {:?}",pick(97,222,333));println!("picked a tuple: {:?}",pick(28,("dog",1),("cat",2)));
๐ก
Rust infers a type for T based on the types of the arguments and return value, and turns generic code into non-generic code accordingly. Therefore, this is a zero-cost abstraction: you get exactly the same result as if you had hand-coded the data structures without the abstraction.
T is specified twice in impl<T> Point<T> {} because it is a generic implementation section for generic type.
๐ก
It is possible to write impl Point<u32> { .. }. Now Point is still generic and you can use Point<f64>, but methods in this block will only be available for Point<u32>.
fnduplicate<T:Clone>(a:T)->(T,T){(a.clone(), a.clone())}#[derive(Debug)]// #[derive(Clone)] // try to uncomment this linestructNotClonable{
name:String,
age:i32,}let foo =String::from("foo");let foo_pair =duplicate(foo);println!("{foo_pair:?}");let me =NotClonable{ name:String::from("Ethan"), age:23};let me_pair =duplicate(me);println!("{bar_pair:?}");
๐ก
The struct NotClonable is not clonable because the trait Clone is not implemented for it
impl Trait
fnadd_42_millions(x:implInto<i32>)->i32{
x.into()+42_000_000}fnpair_of(x:u32)->implstd::fmt::Debug{(x +1, x -1)}let many =add_42_millions(42_i8);println!("{many}");let many_more =add_42_millions(10_000_000);println!("{many_more}");let debuggable =pair_of(27);println!("debuggable: {debuggable:?}");
๐
fn add_42_millions(x: impl Into<i32>) can be seen as a syntactic sugar for fn add_42_millions<T: Into<i32>>(x: T)
๐ก
Using impl Trait as return type can be useful when you donโt want to expose the concrete type in a public API.
Standard Library Types
Option
let name ="Lรถwe ่่ Lรฉopard Gepardi";letmut position:Option<usize>= name.find('รฉ');println!("find returned {position:?}");assert_eq!(position.unwrap(),14);
position = name.find('Z');println!("find returned {position:?}");assert_eq!(position.expect("Character not found"),0);
๐
unwrap will return the value in an Option, or panic. expect is similar but takes an error message.
Option<T> often has the same size in memory as T.
๐
take
Result
usestd::fs::File;usestd::io::Write;usestd::io::Read;letmut file =File::create("diary.txt").unwrap();
file.write_all(b"When did I write something here?\nI don't remember!").expect("failed to write message");let file:Result<File,std::io::Error>=File::open("diary.txt");match file {Ok(mut file)=>{letmut contents =String::new();ifletOk(bytes)= file.read_to_string(&mut contents){println!("Dear diary: \n{contents} \n({bytes} bytes)");}else{println!("Could not read file content");}}Err(err)=>{println!("The diary could not be opened: {err}");}}
When did I write something here?
I don't remember!
It is an generic enum with the variants, Ok(T), representing success and containing a value, and Err(E), representing error and containing an error value.
String
letmut s1 =String::new();
s1.push_str("Hello");println!("s1: {s1} len = {}, capacity = {}", s1.len(), s1.capacity());letmut s2 =String::with_capacity(s1.len()+1);
s2.push_str(&s1);
s2.push('!');println!("s2: {s2} len = {}, capacity = {}", s2.len(), s2.capacity());
s2.push('!');println!("s2: {s2} len = {}, capacity = {}", s2.len(), s2.capacity());let s3 =String::from("๐จ๐ญ");println!("s3: {s3} len = {}, capacity = {}, number of chars = {}", s3.len(), s3.capacity(), s3.chars().count());for(i, c)in s3.chars().enumerate(){println!("char {i}: {c}");}
๐ก
When a type implements Deref<Target = T>, the compiler will let you transparently call methods from T.
String implements Deref<Target = str>, so we can call all str methods on a String.
โ ๏ธ
Note that a char can be different from what a human will consider a โcharacterโ due to grapheme clusters.
Vec
letmut v1 =Vec::new();
v1.push(42);println!("v1: {:?}, len = {}, capacity = {}", v1, v1.len(), v1.capacity());letmut v2 =Vec::with_capacity(v1.len()+1);
v2.extend(v1.iter());
v2.push(9999);println!("v2: {:?}, len = {}, capacity = {}", v2, v2.len(), v2.capacity());
v2.push(1026);println!("v2: {:?}, len = {}, capacity = {}", v2, v2.len(), v2.capacity());// Canonical macro to initialize a vector with elements.letmut v3 =vec![0,0,1,2,3,4];// Retain only the even elements.
v3.retain(|x| x %2==0);println!("v3: {v3:?}");// Remove consecutive duplicates.
v3.dedup();println!("v3: {v3:?}");
v3.push(0);println!("v3: {v3:?}");
v3.dedup();println!("v3: {v3:?}");// Use vector to store UTF-8 encoded strings.letmut v4 =Vec::new();
v4.extend(String::from("Hello ๐ฆ").chars());println!("v4: {v4:?}");
๐ก
vec![...] is a canonical macro to use instead of Vec::new() , which supports adding initial elements to the vector.
๐
To index the vector you use [], but they will panic if out of bounds. Alternatively, using get will return an Option. The pop function will remove the last element.
HashMap
usestd::collections::HashMap;letmut page_counts =HashMap::new();
page_counts.insert("Adventures of Huckleberry Finn".to_string(),207);
page_counts.insert("Grimms' Fairy Tales".to_string(),751);
page_counts.insert("Pride and Prejudice".to_string(),303);if!page_counts.contains_key("Les Misรฉrables"){println!("We know about {} books, but not Les Misรฉrables.",
page_counts.len());}for book in["Pride and Prejudice","Alice's Adventure in Wonderland"]{match page_counts.get(book){Some(count)=>println!("{book}: {count} pages"),None=>println!("{book} is unknown."),}}// Use the .entry() method to insert a value if nothing is found.for book in["Pride and Prejudice","Alice's Adventure in Wonderland"]{let page_count:&muti32= page_counts.entry(book.to_string()).or_insert(0);*page_count +=1;}{let pc1 = page_counts.get("Harry Potter and the Sorcerer's Stone").unwrap_or(&336);println!("Harry Potter has {} pages", pc1);}println!("page_counts:\n{page_counts:#?}");
๐ก
Unlike vec!, there is no standard hashmap! macro. But since Rust 1.56, HashMap implements From<[(K, V); N]>, which allows us to easily initialize a hash map from a literal array:
let page_counts =HashMap::from([("Harry Potter and the Sorcerer's Stone".to_string(),336),("The Hunger Games".to_string(),374),]);
The == and != operators will call required method eq and provided method ne in PartialEq trait.
๐ก
The โPartialโ here implies only a portion of elements of the given type can be compared.
For instance, float only implements PartialEq but not Eq because NaN is also a float number but cannot be compared. See Rust course for details.
โ ๏ธ
PartialEq can be implemented between different types, but Eq cannot, because it is reflexive
let value:i64=1000;println!("as u16: {}", value asu16);println!("as i16: {}", value asi16);println!("as u8: {}", value asu8);
๐
Rust has no implicit type conversions, but does support explicit casts with as.
โ ๏ธ
Casts are best used only when the intent is to indicate unconditional truncation (e.g. selecting the bottom 32 bits of a u64 with as u32, regardless of what was in the high bits).
structDroppable{
name:&'staticstr,}implDropforDroppable{fndrop(&mutself){println!("Dropping {}",self.name);}}{let a =Droppable{ name:"a"};{let b =Droppable{ name:"b"};{let c =Droppable{ name:"c"};let d =Droppable{ name:"d"};println!("Exiting block B");}println!("Exiting block A");}drop(a);println!("Exiting main");}
Smart Pointers
Box
Box in Rust is like std::unique_ptr in C++, except that itโs guaranteed to be not null.
let five =Box::new(5);println!("five: {}",*five);
๐
Box<T> implements Deref<Target = T>, which means that you can call methods from T directly on a Box<T>.
#[derive(Debug)]enumList<T>{/// A non-empty list: first element and the rest of the list.Element(T,Box<List<T>>),/// An empty list.Nil,}let list:List<i32>=List::Element(1,Box::new(List::Element(2,Box::new(List::Nil))));println!("{list:?}");
๐ก
A Box can be useful when you want to transfer ownership of a large amount of data. To avoid copying large amounts of data on the stack, instead store the data on the heap in a Box so only the pointer is moved.
RC
Rc in Rust is like std::shared_ptr in C++.
usestd::rc::Rc;let a =Rc::new(10);println!("a: {a}");println!("a strong: {}",Rc::strong_count(&a));let b =Rc::clone(&a);println!("b: {b}");println!("a strong: {}",Rc::strong_count(&a));println!("b strong: {}",Rc::strong_count(&b));
๐ก
Use Rc::strong_count to check the reference count.
Cell wraps a value and allows getting or setting the value, even with a shared reference to the Cell. However, it does not allow any references to the value.
Rc only allows shared (read-only) access to its contents, since its purpose is to allow (and count) many references.
#[derive(Debug)]structHighlight<'doc>(&'docstr);fnerase(text:String){println!("Bye {text}!");}{let text =String::from("The quick brown fox jumps over the lazy dog.");let fox =Highlight(&text[4..19]);let dog =Highlight(&text[35..43]);// erase(text);println!("{fox:?}");println!("{dog:?}");};
The Iterator trait tells you how to iterate once you have created an iterator.
The trait IntoIterator defines how to create an iterator for a type. It is used automatically by the for loop.
FromIterator
let primes =vec![2,3,5,7];let prime_squares = primes.into_iter().map(|p| p * p).collect::<Vec<_>>();println!("prime_squares: {prime_squares:?}");
๐ก
The above code works because the trait Iterator implements
//! Colorized output utilities for the terminal using ANSI escape codes.//! # Examples://! ```//! use cli_utils::colors::*;//! println!("{}{}{}", red("Red"), green("Green"), blue("Blue"));//! ```/// Returns a string with the ANSI escape code for red./// # Examples:/// ```/// use cli_utils::colors::*;/// println!("{}", red("Red"));/// ```pubfnred(s:&str)->String{format!("\x1b[31m{}\x1b[0m", s)}
# test the color modulecargotest--doc--package cli-utils -- colors --nocapture# test the function red in the color modulecargotest--doc--package cli-utils -- colors::red --nocapture
Unit test
#[test]fntest_fib(){assert_eq!(fib(1),1);assert_eq!(fib(2),1);assert_eq!(fib(4),3);assert_eq!(fib(6),8);}fnfib(n:u32)->u32{if n <=2{// The base case.return1;}else{// The recursive case.returnfib(n -1)+fib(n -2);}}
๐ก
Decorate a function with #[test] and run cargo test to do unit test
Documentation
/// Returns a string with the ANSI escape code for red./// # Examples:/// ```/// use cli_utils::colors::*;/// println!("{}", red("Red"));/// ```pubfnred(s:&str)->String{format!("\x1b[31m{}\x1b[0m", s)}
# generate documentations at ./target/doc/cargo doc