Introduction
In a world where AI suppose to write code instead of you, to replace teachers, friends and lovers.
This book is an rubberducking for me to learn rust.
Someone told me once, when you can explain complicated concept in such way that others can understand easily
then you have mastered that concept (I don’t remember who told me that).
The original idea was to touch those fundamental Rust concepts in order to facilitate the first impact of someone who have just started with Rust.
Chapters are organized in a didactical logic instead of cluster of logical topics, which means starting with small and simple concepts then evolving in more complex things.
When possible i will try to link directly the Rust Book in order to avoid duplication.
A lot is inspired by the official learning materials provided by Rust, please read them they are the best.
why rust?
Frankly there are too many video already available online 🤯.
A non exaustive list of good reasons:
- Good defaults.
- Restrictive but still possible to opt out.
- Safe defined as non Undefined Behaviour.
- Good tooling.
- Maximized checks on compile time.
Some personal opinion: There is a good complain in the community that rust is slow to compile compared to C++, I would argument that the C++ compiler time is even longer: in order to make a safe C++ code it is necessary to run Static Code Analysis and Dynamic Code Analysis, since for each of this tool have several vendors that give different results, you may need to run more than one, now sum the time needed to run all this tools and the effort to rework the code to make it conform to the findings and you will have your answer of which language is the slowest to compile.
I am gloss over the huge effort of maintaining the tooling for C/C++ ultimately making the language inefficient:
- make, ninja, CMake, Bazel, Mason.
- list Static Code Analysis
- Clang tools
- almost for each Clang tool gcc has an equivalent.
Rust Lang shakes away that constant fear of shooting yourself on the foot.
Playground
For those that want to have an initial taste of Rust and who want to avoid the hustle to setup the there are a couple of possible playgrounds.
mdbook playground
You can edit and run rust code directly in the editable code block:
fn main() { // you can modify the code here! println!("Hello World!"); }
Container
You can use the container provided with the repository of this book.
git clone https://github.com/shelby1eo/ya-rust-introduction
cd ya-rust-introduction
make all-interactive
cd examples/hello-world/
cargo build
cargo run
Aquascope Playground
cognitive-engineering-lab provides an interactive visualizations of Rust programs called Aquascope Playground.
Install on your own host
Please follow the instruction provided by your distribution, here some good link:
nixos rust Installation
Rust book chapter Installation
VariablesDifferent way
Declaration
// print_type_of ref: ref: https://stackoverflow.com/questions/21747136/how-do-i-print-the-type-of-a-variable-in-rust fn print_type_of<T>(_: &T) { println!("{}", std::any::type_name::<T>()); } fn main() { // The type is specified after the variable name. let a: u8 = 42; print_type_of(&a); // i32 is the default if not specified. let a = 42; print_type_of(&a); // type can be coerced. let a = 42u64; print_type_of(&a); }
Mutability
Variable is inmutable by default, followinf code will fail:
fn main() { let a = 3; a = 6; }
mut is used to make a variable mutable:
fn main() { let mut a = 3; a = 6; }
Scope & shadowing
Variables can be shadowed: it is possible to declare the same variable name, following portion of the code will only see the last declaration.
fn main() { let a:i32 = 1; let a_ptr: *const i32 = &a; println!("{} {:?}", a, a_ptr); let a: f32 = 2.0; let a_ptr: *const f32 = &a; println!("{} {:?}", a, a_ptr); let a = 3u64; let a_ptr: *const u64 = &a; println!("{} {:?}", a, a_ptr); }
Scope works similar to C++ which is the secrion contained by a {}
fn main() { let a:i32 = 1; let a_ptr: *const i32 = &a; println!("{} {:?}", a, a_ptr); let a: f32 = 2.0; let a_ptr: *const f32 = &a; println!("{} {:?}", a, a_ptr); { // this is a different scope, variables declated here are droped at the end of it. let a = 3u64; let a_ptr: *const u64 = &a; println!("{} {:?}", a, a_ptr); } println!("{} {:?}", a, a_ptr); }
Data Types
For deep dive please read The Rust Book Data types.
Here we are going to look in details to the following points:
- u32
- array/vector
- String/Str
- Stack/Heap
Numeric Types
To a first glance built-in integer types are very similar to the types available in C/C++. Table 2-1: Integer Types in Rust
Length | Signed | Unsigned |
---|---|---|
8-bit | i8 | u8 |
16-bit | i16 | u16 |
32-bit | i32 | u32 |
64-bit | i64 | u64 |
128-bit | i128 | u128 |
arch-bit | isize | usize |
u32 basics
Actually rust build-in types are basically classes std u32.
Rust does most of the checks in compiling time, try to execute the following code blocks.
When possible overflow are detected at compile time.
fn main() { let a = 200u8; // type specified let b = 200; // type inference let _c = a + b; }
The default reaction of rust is to panic in case of runtime error.
#![feature(strict_overflow_ops)] fn main() { let a = 200u8; let b = 200u8; let _c = a.strict_add(b); }
Types are more similar to classes with convenient basic functionalities.
fn main() { let a = 200u8; let b = 200u8; let c = a.saturating_add(b); println!("{c}"); }
fn main() { let a = 200u8; // by default all variables are immutable let mut c = a.checked_add(1); // mut: to declare a mutable variable println!("{:?}", c); let mut c = (u32::MAX - 2).checked_add(3); println!("{:?}", c); }
Array/Vector
Stack is faster, but it size need to be fixed at compilation time. Heap is more flexible (e.g. can be grown) but it is more costly in term of access time.
Array are located in the Stack:
Array cannot be grown, but you can slice it:
fn main() { let ji = ["Pollo", "Chicken", "Pavo", "Poulet"]; let dinner = &ji[0 .. 3]; println!("{:?}", ji); println!("{:#?}", dinner); }
It is possible to put the variable into the heap:
- Box: more efficient, efficient when want to transfer ownership of big data.
- Vec: growable, has more feature than Box with the cost of using more memory.
Vec are over allocated because when reaching its maximun size the full array need to be reallocated and the memory copied.:
Play here:
#![allow(unused)] #![feature(vec_into_raw_parts)] fn main() { let mut array: [i32; 3] = [0; 3]; let bytes: [u8; 3] = [1, 0, 2]; let months = ["Pollo", "Chicken", "Pavo"]; array[0] = 5; let boxed_slice: Box<[u8]> = vec![0; 100_000_000].into_boxed_slice(); let array2: Box<[i32]> = Box::new([1,2,3]); // demo memory reallocation: let mut vec = Vec::new(); let vec_ptr = vec.as_ptr(); println!("pointer to non allocated memory to address {:?}", vec_ptr); vec.push(1); // by default this will over allocate a capacity of 4 // allocate vec2 order to occupy the memory after vec so it will be forced to reallocate once hit the size 4 let mut vec2 = Vec::new(); vec2.push(5); let mut vec_ptr = vec.as_ptr(); // aliasing println!("1: {:?}", vec_ptr); vec.push(1); vec_ptr = vec.as_ptr(); println!("2: {:?}", vec_ptr); vec.push(1); vec_ptr = vec.as_ptr(); println!("3: {:?}", vec_ptr); vec.push(1); vec_ptr = vec.as_ptr(); println!("4: {:?}", vec_ptr); vec.push(1); vec_ptr = vec.as_ptr(); println!("5: cap is hit, reallocation and copy {:?}", vec_ptr); vec.push(1); vec_ptr = vec.as_ptr(); println!("6: {:?}", vec_ptr); }
std::vec and std:boxed are logically related related, one is growable the other is not.
but the init syntax is very different:
fn main() { // std::vec init with no elements: let mut a: Vec<i32> = Vec::new(); // std::vec init with elements: let mut a: Vec<i32> = Vec::from([1,2,3]); // std::vec init with element simplified by a macro: let mut a: Vec<i32> = vec![1,2,3]; // std::boxed has predefined size // the type and the declaration looks so different!!! let mut a: Box<[i32]> = Box::new([1,2,3]); }
String/Str
String
Rust has several String types, to go deep into the rabbit hole: All Rust string types explained.
Here we gonna stick to the simple std::String and std::str.
Lets Start by referencing good sources:
LSS(Long Story Short): In rust all strings are encode in UTF-8 we will gloss over the advantages and reasons, google docet.
The important thing to know is that the difference from ASCII where each symbol is represented by a byte,
in UTF-8 a symbol can be represented by up to 4 bytes.
// lets take the following 3 examples: let hello = String::from("ciao"); // c(u0063) i(u0069) a(u0061) o(u006f) // 你(u4f60) 好(u597d) it seems that Chinese is not so complicated ^^. let hello = String::from("你好"); // न(u0928) म(u092e) स्(u0938 u094d) ते(u0924 u0947) you cannot count chars by eyes anymore. let hello = String::from("नमस्ते"); // this is super critical. let hello = String::from("🐖💨💩");
String manipulation
Basic String manipulation
The official docs has some good example.
fn main(){ // How to declare a string let mut string = String::new(); println!("{}", string); let mut string = String::from("💩💩💩💩"); println!("{}", string); // basic string manipulation let mut string = String::new(); string.push('🐖'); // Push a utf8 character. println!("{}", string); string.push_str(", "); // Push a string. println!("{}", string); string.push_str("नमस्ते"); println!("{}", string); let ptr = string.as_ptr(); // Check address of the first character. println!("{:?}", ptr); string = string.replace("🐖", "💩"); // Will this allocate new memory? (next chapter) println!("{}", string); let ptr = string.as_ptr(); println!("{:?}", ptr); let string2 = String::from("💩💩💩💩"); string = string + &string2; // String concatenation. (next chapter) println!("{}", string); }
different type of strings
Rust has several types of strings, here we gonna only to show the two basics ones:
- std::strings is a string.
- std::str is the string literal or a slice.
An exaustive explanation of all strings type can be found here All Rust string types explained.
fn print_type_of<T>(_: &T) { println!("{}", std::any::type_name::<T>()); } fn main() { // Rust has several string types: // string literal stored into the stack and they are of type &str let my_str: &str = "initial contents 🍄"; print!("String literal has type: "); print_type_of(&my_str); // It can be converted in a String type print!("to_string will convert it to the type: "); let mut my_string = my_str.to_string(); print_type_of(&my_string); my_string.push_str("aa"); let string2 = String::from("123456"); // it is also possible to get the pointer to the string container. println!("my_str_ptr = {:?} stack", my_str.as_ptr()); println!("my_string_ptr = {:?} heap", my_string.as_ptr()); println!("String2_ptr = {:?} heap", string2.as_ptr()); // slices println!(""); println!("##############################################"); println!("slices std::str can point to both stack and heap"); println!("my_str_slice_ptr = {:?} stack", &my_str[1..my_str.len()].as_ptr()); println!("String2_slice_ptr = {:?} heap", &string2[1..string2.len()].as_ptr()); }
Indexing into Strings
Rust do not allow you directly to index over a String. try to execute in order to get the compiler error:
fn main() { let hello = String::from("नमस्ते"); let b = hello[0]; }
The reason is that a String in rust is more complex that a String in C which is just an array of char with a terminating ‘\0’. A String in Rust is implemented as a wrapper over Vec<>, to see the details click on the binoculars:
Furthermore since in Rust a String is encoded as UTF-8 not all characters are rapresented by a char, for example स् is rapresented by u0938 u094d. Rust took the choise to not allow direct indexing may lead to confusion.As pattern that you will see by going deeper in Rust, the default behaviour of rust is very strict and safe, but it always allows you to do everything, it just requires an extra effort. A way to index an String is to use a slice:
fn main() { let hello = String::from("नमस्ते"); let slice_hello = &hello[0..3]; println!("{}", slice_hello); println!("{:?}", slice_hello.as_bytes()); println!("{}", slice_hello.as_bytes()[0]); }
The first logic question would be, why bother to slice it, why not allowing direct indexing? The answer is because during the slicing Rust perform for you a bondary check in such way that you would not slice a UTF-8 character in half. Try to execute the following code:
fn main() { let hello = String::from("नमस्ते"); let slice_hello = &hello[0..2]; }
A more apropriate way to iterate over UTF-8 chars:
fn main() { #![allow(unused)] fn main() { for c in "[]Зд".chars() { println!("{c}"); } }
move-semantics
The title is a little misleading in this chapter we will focus on what the assignment operator = .
In Cpp there is the concept of move semantic for those familiar with it here some more readings:
Long Story Short, the move semantics move the ownership of heap allocated memory to the variable that is assigned to. The concept is the same as in C++ but rust have it as default behaviour so no need of extra syntax. For sake of optimization copy are avoided, but if necessary it need to be explicit.
type | behaviour | comment |
---|---|---|
scalar | copy | |
array | copy | |
vector | move | The original variable is not accessible anymore |
String | move | The original variable is not accessible anymore |
String literal | copy |
Scalar
Not much to say here, variables on the stack are not effected. For scalar there the move semantics is not really applicable since the memory get copied and the Copy trait is implemented.
fn main() { let a = 5; println!("{a}"); let mut b = a; b = 1; println!("{b}"); println!("{a}"); }
Array
Not much to be said here, memory allocated on the stack and the default behaviour is a copy. In later chapter will be shown how to avoid this memory copy via borrowing.
Vector
Finally things get interesting, in Rust when the memory is allocated on the heap the operator = will move the ownership of the memory to the assigned variable, note that the original variable will not be accessible anymore:
fn main(){ let a = Box::new([1, 2, 3]); let b = a; println!("{:?}", b); println!("{:?}", a); // a have lost the access on that memory this line will not compile. }
String
Since string is a wrapper over Vec the same behaviour happens here.
String literal
fn main(){ let a = "aaaa"; let b = a; println!("a_ptr = {:?}", a.as_ptr()); println!("b_ptr = {:?}", b.as_ptr()); println!("{}", a); println!("{}", b); }
Ownership
In order to assure memory safety the following 3 rules are enforced by the compiler:
- Each value in Rust has an owner.
- There can only be one owner at a time.
- When the owner goes out of scope, the value will be dropped.
The operators that have an effect on the ownership are:
operator | name | Column3 |
---|---|---|
= | Assignment | when move, the ownership is also transfered |
& | Borrow | ownership is transfered. ownership is returned once new owner goes out of the scope |
Scalar
Assignment operator =
Not much to be said about Simple Assignment: of a scalar, It is a copy the memory to another address on the stack. No ownership is transferred.
Borrow operator &
ref: rust-lang borrow operator &. ref: rust-lang dereference operator *.
rust-lang defines the scope as:
“A scope is the region of source text where a named entity may be referenced with that name.”
To keep it simple know a scope is the text enclosed by a pair of braces {}
or when the variable is last used.
At the end of the scope of b the ownership is returned to a.
fn main() { let mut a = 5; println!("{a}"); { let b = &mut a; // the ownership of the memory of a is transferred to b. *b = 1; // a is not accessible because memory ownership is transfer to b // println!("{a}"); // !!! try to uncomment this line. println!("{b}"); println!("{}", *b); // b goes out of the scope, so a regain the ownership. } println!("{a}"); }
Array
Assignment operator =
Not much to be said about Simple Assignment: of a scalar, It is a copy the memory to another address on the stack. No ownership is transferred.
Borrow operator &
By borrowing it is possible to avoid unnecessary memory copy:.
At the end of the scope of b the ownership is returned to a.
Vector
Since String is just a wrapper to Vec, it will be omitted in this chapter.
Assignment operator =
The ownership is moved to b and at the end of its scope the memory is dropped.
fn main() { let mut a: Vec<i32> = Vec::new(); a.push(1); println!("{:?}", a); { let mut b = a; b.push(2); println!("{:?}", b); } // println!("{:?}", b); // println!("{:?}", a); }
Borrow operator &
At the end of the scope of b the ownership is returned to a.
fn main() { let mut a: Vec<i32> = Vec::new(); a.push(1); println!("{:?}", a); { let b = &mut a; b.push(2); println!("{:?}", b); } // println!("{:?}", b); println!("{:?}", a); }
Permissions
By default in rust all variables are not mutable which means that they have only read permissions.
Immutable variables enable the compiler to better optimize the code.
It is arguably a best practice in any programming language to make mutable variable only when it is necessary, defaulting to this behaviour avoid you to add const or constexpr infromt of all variable declaration therefore making the code cleaner.
Permissions-Scalar
mutable reference
Very similar to the const pointer to const variable in C and C++ but the positioning of the mut keyword is more intuitive:
- Mutable reference: this means that the referenced memory can be modified.
#![allow(unused)] fn main() { let mut a = 5; let b = &mut a; *b += 1; }
- Mutable reference to immutable variable.
#![allow(unused)] fn main() { let a = 5; let b = &mut a; // cannot borrow as mutable since a was declared as immutable. *b += 1; }
- b cannot be reassigned to reference another variable, because it is immutable.
#![allow(unused)] fn main() { let mut a = 5; let mut c = 66; let b = &mut a; // immutable reference to mutable variable *b += 1; b = &mut c; *b += 1; }
- When b is mutable it can be reassigned.
#![allow(unused)] fn main() { let mut a = 5; let mut c = 66; let mut b = &mut a; // mutable reference to mutable variable *b += 1; println!("{b}"); b = &mut c; *b += 1; println!("{b}"); }
mutable reference effects on permissions
Lets take the following 4 usecases.
cases | Comment |
---|---|
b = &a; | |
b = &mut a; | |
mut b = &a; | |
mut b = &mut a; |
b = &a;
- a is declared.
- b takes ownership of a but only with R permission.
- both a and b are accessible in R only.
- once b is last used in the current scope, a will recover its W permission.
b = &mut a;
- a is declared.
- b takes ownership of a but only with R permission.
- b are accessible inre R only but a lose the R permission.
- once b is last used in the current scope, a will recover its W permission.
mut b = &a;
- a is declared.
- b takes ownership of a but only with R permission.
- b is reassigned to mut reference to c.
- b’s permission still is defined from the first assignment
#![allow(unused)] fn main() { let mut a = 5; let mut c = 66; let mut b = &a; // *b += 1; println!("{b}"); b = &mut c; a += 1; println!("{b}"); *b += 1; // this line will not compile println!("{b}"); }
mut b = &mut a;
- a is declared.
- b takes ownership of a but with W permission.
- b is reassigned to mut reference to c
- b type is decided from the first assignment
#![allow(unused)] fn main() { let mut a = 5; let mut c = 66; let mut b = &mut a; *b += 1; println!("{b}"); b = &mut c; a += 1; println!("{b}"); *b += 1; println!("{b}"); }
Permissions-Array
mutable reference effects on permissions
cases | Comment |
---|---|
b = &a; | |
b = &mut a; | |
mut b = &a; | |
mut b = &mut a; |
b = &a;
- a is declared.
- b takes ownership of a but only with R permission.
- both a and b are accessible in R only.
- once b is last used in the current scope, a will recover its W permission.
fn main() { let mut a: [u8; 3] = [2; 3]; let b = &a; // b[0] += 1; println!("{:?}", b); println!("{:?}", a); }
b = &mut a;
- a is declared.
- b takes ownership of a but only with R permission.
- b are accessible inre R only but a lose the R permission.
- once b is last used in the current scope, a will recover its W permission.
fn main() { let mut a: [u8; 3] = [2; 3]; let b = &mut a; b[0] += 1; println!("{:?}", b); println!("{:?}", a); }
mut b = &a;
- a is declared.
- b takes ownership of a but only with R permission.
- b is reassigned to mut reference to c.
- b’s permission still is defined from the first assignment
fn main() { let mut a: [u8; 3] = [2; 3]; let mut b = &a; let mut c: [u8; 3] = [1; 3]; //b[0] += 1; b = &mut c; //b[0] += 1; println!("{:?}", b); println!("{:?}", a); }
fn main() { let mut a: [u8; 3] = [2; 3]; let mut b = &a; let mut c: [u8; 3] = [1; 3]; b = &mut c; }
mut b = &mut a;
- a is declared.
- b takes ownership of a but with W permission.
- b is reassigned to mut reference to c
- b type is decided from the first assignment
fn main() { let mut a: [u8; 3] = [2; 3]; let mut b = &mut a; let mut c: [u8; 3] = [1; 3]; b[0] += 1; b = &mut c; b[0] += 1; println!("{:?}", b); println!("{:?}", a); }
variable scope
Since arrays are store in the stack the behaviour is similar to the scalar variables.
fn main() { let mut a: [u8; 3] = [2; 3]; println!("{:?}", a); { let b = &mut a; b[0] += 1; println!("{:?}", b); } // println!("{:?}," b); // b is out of the scope and the memory is freed. println!("{:?}", a); }
Permissions-Vector
Since String is just a wrapper to Vec, it will be omitted in this chapter.
mutable reference effects on permissions
cases | Comment |
---|---|
b = &a; | |
b = &mut a; | |
mut b = &a; | |
mut b = &mut a; |
b = &a;
- a is declared.
- b takes ownership of a but only with R permission.
- both a and b are accessible in R only.
- once b is last used in the current scope, a will recover its W permission.
fn main() { let mut a: Vec<i32> = Vec::from([1, 2, 3]); let b = &a; // b[0] += 1; println!("{:?}", b); println!("{:?}", a); }
b = &mut a;
- a is declared.
- b takes ownership of a but only with R permission.
- b are accessible inre R only but a lose the R permission.
- once b is last used in the current scope, a will recover its W permission.
fn main() { let mut a: Vec<i32> = Vec::from([1, 2, 3]); let b = &mut a; b[0] += 1; println!("{:?}", b); println!("{:?}", a); }
mut b = &a;
- a is declared.
- b takes ownership of a but only with R permission.
- b is reassigned to mut reference to c.
- b’s permission still is defined from the first assignment
fn main() { let mut a: Vec<i32> = Vec::from([1, 2, 3]); let mut b = &a; let mut c: Vec<i32> = Vec::from([4, 5, 6]); //b[0] += 1; b = &mut c; //b[0] += 1; println!("{:?}", b); println!("{:?}", a); }
mut b = &mut a;
- a is declared.
- b takes ownership of a but with W permission.
- b is reassigned to mut reference to c
- b type is decided from the first assignment
fn main() { let mut a: Vec<i32> = Vec::from([1, 2, 3]); let mut b = &mut a; let mut c: Vec<i32> = Vec::from([4, 5]); b[0] += 1; b = &mut c; b[0] += 1; println!("{:?}", b); println!("{:?}", a); }
Functions
Statement-Expression
some theory
Rust is a expression-based language. Fundamental definitions are:
- Statements: are instructions that perform some action and do not return a value.
#![allow(unused)] fn main() { let a = 1; // is an statement. }
#![allow(unused)] fn main() { let a = (let b = 1); // since it do not return anything, a statement cannot be part of an assignment. }
- Expressions: evaluate to a resultant value.
#![allow(unused)] fn main() { let a = { let b = 1; b + 1 // this is an expression. // an expression at the end of a scope is the return value of the scope. }; }
#![allow(unused)] fn main() { let a: u32 = { let b = 1u32; b + 1; // the semicolon at the end transforms an Expression into a Statement. }; }
For details please check the Rust Book.
Statement-Signature
basic function signature
The order of the variable name and the type is different that C and C++, it is positioned in order to allow you to read from left to right.
fn my_function(my_arg1: u32) -> u32 { if my_arg1 > 10 { // return keyword can be used for early return. return my_arg1 - 1; } // return and semicolon can be omitted. my_arg1 + 1 } fn main() { // modify the value of a and try to execute this code. let a = 10u32; let b = my_function(a); println!("{}", b); }
Statement-Arguments
Passing argument by value
By default the arguments are passed by value, but attention, as we have already seen in the previous chapter, the operator = behaves differently depending on the type:
fn myfunc(arg1: u32, arg2: Vec<u32>){ println!("{}", arg1); println!("{:?}", arg2); } fn main(){ let mut a = 10; let mut b: Vec<u32> = Vec::from([1, 2, 3]); myfunc(a, b); println!("{}", a); println!("{:?}", b); }
Passing argument by reference.
fn myfunc(arg1: &mut u32, arg2: &mut Vec<u32>){ *arg1 += 10; arg2[1] += 10; println!("{}", *arg1); println!("{:?}", *arg2); } fn main(){ let mut a = 10; let mut b: Vec<u32> = Vec::from([1, 2, 3]); myfunc(&mut a, &mut b); a += 1; b[0] += 1; println!("{}", a); println!("{:?}", b); }
Statement-Return
keyword return
The keyword return is not necessary for returning expression at the end of the scope. The keyword return can be used if to return before the end of the scope.
fn my_function(my_arg1: u32) -> u32 { if my_arg1 > 10 { // return keyword can be used for early return. return my_arg1 - 1; } // return and semicolon can be omitted. my_arg1 + 1 } fn main() { // modify the value of a and try to execute this code. let a = 10u32; let b = my_function(a); println!("{}", b); }
Truple
A tuple is a collection of values of different types.
It can be useful to:
- return several variables from a function
- group variables that are logically bounded.
return several values
fn myfunc() -> (u32, Vec<i32>) { let a = 42u32; let b = Vec::from([8, 9]); (a, b) } fn main(){ let c = myfunc(); println!("{:?}", c); }
Truple can be named
fn myfunc() -> (u32, u32) { let coord: (u32, u32) = (1u32, 2u32); coord } fn myfunc2() -> (u32, u32) { let x = 4u32; let y = 5u32; let coord: (u32, u32) = (x, y); coord } fn main(){ let c = myfunc(); println!("{:?}", c); let c = myfunc2(); println!("{}", c.0); println!("{}", c.1); }
as argument
fn myfunc(arg1: (u32, i32)) { println!("{:?}", arg1); } fn main(){ let c = (1, -1); myfunc(c); }
Ownership
We have already show that passing or returning a Vector or String would result also in passing the ownership of its memory.
Lets observe in the following example this interaction:
- In rust there are not function prototypes, so the order where the functions are written doesn’t matter.
- Pay attention to the ownership of the String.
- it is extremely clear what is the in and out of the function.
- runtime impact is minimum since the memory is moved instead of copied.
fn main(){ let mut a: String = String::from("TA"); a.push_str("C"); println!("{:?}", a); let b = myfunc(a); println!("{:?}", b); } fn myfunc(mut arg1: String) -> String { println!("{:?}", arg1); arg1.push_str("OS"); arg1 }
Struct
Truple Struct
Enum
ToDo: enum with different types
Error-handling
Lifetime
dependencies
ControlFlows
Just read the chapter in The Rust Book ^^.
shelby.leo@proton.me
github
buyMeACoffee
Placeholder
Annex-1
The documentation of the interpreter aquascope do not document the options, here are some example of usage.
interpreter
aquascope,interpreter,horizontal
aquascope,interpreter,concreteTypes
aquascope,interpreter+permissions,boundaries,stepper,horizontal
permissions
aquascope,permissions,stepper
aquascope,permissions,boundaries,showFlows
aquascope,permissions,boundaries,stepper
Annex-2 Rust vs C++
feature | rust | C++ | comment |
---|---|---|---|
const | default | cost or constexp | enable better compiler opt |
move semantics | default | need explicit std::move(var) | the c++ syntax is heavy |