Just a reminder on how I setup my machine for Rust dev
Homebrew (Mac)
in terminal
brew install rustup
in terminal
rustup-init
choose default option: 1 at prompt
now restart terminal to get new path picked up
in terminal
rustc
should return bunch of rustc command doco
in terminal
~/Code/$ cargo new myProject
open vscode (assuming you setup the "code" terminal shortcut
~/Code/myProject/$ code .
save vscode workspace for the project
CMD+SHIFT+P
select: Extensions: install extensions
Install the following:
- Rust (basic language support)
- Tabnine (additional intellisense)
In the root of your project (containing the Cargo.toml file) create a folder called .vscode. Inside .vscode, create a file called tasks.json and place the content of this gist inside the json file.
Then press CMD+SHIFT+B or F5 when your main.rs is open.
Referring to https://code.visualstudio.com/docs/editor/tasks#vscode in order to setup tasks create .vscode/tasks.json file and populate with text below providing build and run task.
// Available variables which can be used inside of strings.
// ${workspaceRoot}: the root folder of the team
// ${file}: the current opened file
// ${fileBasename}: the current opened file's basename
// ${fileDirname}: the current opened file's dirname
// ${fileExtname}: the current opened file's extension
// ${cwd}: the current working directory of the spawned process
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"args": ["build", "-v"],
"command": "cargo",
"group": "build",
"problemMatcher": [
{
"owner": "rust",
"fileLocation": ["relative", "${workspaceRoot}"],
"pattern": {
"regexp": "^(.*):(\\d+):(\\d+):\\s+(\\d+):(\\d+)\\s+(warning|error):\\s+(.*)$",
"file": 1,
"line": 2,
"column": 3,
"endLine": 4,
"endColumn": 5,
"severity": 6,
"message": 7
}
}
]
},
{
"label": "clean",
"args": ["clean"],
"presentation": {
"reveal": "always"
}
},
{
"label": "run",
"args": ["run", "-v"],
"presentation": {
"reveal": "always"
},
"group": "build",
"problemMatcher": [
{
"owner": "rust",
"fileLocation": ["relative", "${workspaceRoot}"],
"pattern": {
"regexp": "^(.*):(\\d+):(\\d+):\\s+(\\d+):(\\d+)\\s+(warning|error):\\s+(.*)$",
"file": 1,
"line": 2,
"column": 3,
"endLine": 4,
"endColumn": 5,
"severity": 6,
"message": 7
}
}
]
},
{
"label": "test",
"args": ["test"],
"presentation": {
"reveal": "always"
},
"group": "test",
"problemMatcher": [
{
"owner": "rust",
"fileLocation": ["relative", "${workspaceRoot}"],
"pattern": {
"regexp": "^(.*):(\\d+):(\\d+):\\s+(\\d+):(\\d+)\\s+(warning|error):\\s+(.*)$",
"file": 1,
"line": 2,
"column": 3,
"endLine": 4,
"endColumn": 5,
"severity": 6,
"message": 7
}
},
{
"owner": "rust",
"fileLocation": ["relative", "${workspaceRoot}"],
"severity": "error",
"pattern": {
"regexp": "^.*panicked\\s+at\\s+'(.*)',\\s+(.*):(\\d+)$",
"message": 1,
"file": 2,
"line": 3
}
}
]
},
{
"label": "bench",
"args": ["bench"],
"presentation": {
"reveal": "always"
},
"group": "test",
"problemMatcher": [
{
"owner": "rust",
"fileLocation": ["relative", "${workspaceRoot}"],
"pattern": {
"regexp": "^(.*):(\\d+):(\\d+):\\s+(\\d+):(\\d+)\\s+(warning|error):\\s+(.*)$",
"file": 1,
"line": 2,
"column": 3,
"endLine": 4,
"endColumn": 5,
"severity": 6,
"message": 7
}
},
{
"owner": "rust",
"fileLocation": ["relative", "${workspaceRoot}"],
"severity": "error",
"pattern": {
"regexp": "^.*panicked\\s+at\\s+'(.*)',\\s+(.*):(\\d+)$",
"message": 1,
"file": 2,
"line": 3
}
}
]
}
]
}
Installing extension
If CodeLLDB (A native debugger extension for VS Code) is not already installed then install the extension via Open command palette using Command+p as (or Control+p) as above and enter
ext install vadimcn.vscode-lldb and enter.
On a MAC press function F5 or via menu using Run -> Start Debugging and select LLDB option. VS Code will display a message stating Cannot start debugging because no launch configuration has been provided. Select default options in order to generate a launch file.
Open src/main.rs and place breakpoint to left of the line number 3 println!(“Hello, world!”)and press F5 or Run -> Start Debugging to start the debugger.
compile a script: rustc file.rs then run binary as ./file
! indicates a macro ; end of line
print complex objects to console: println!("{:?}", array);
cargo package manager
cargo new hello_cargo --bin - creates a "toml" file - creates a src folder
cargo debug -- compile debug cargo run -- compile and run cargo check < no build, validate
all variables immutable by default; need to declare with mut to allow changing "mutable"
Rust is a statically typed language
Data Types
-
scalar > a single value
- integer; default to i32; can assign 64bit with type annotation let x:i64 = 43542354;
- float: f32 and f64 annotation options
- boolean: let f = true; or let f:bool = true
- characters: char type: unicode scalar value; let c = 'z'; SINGLE QUOTES string type: later...
-
compound
- tuples
- group a variety of types
- think struct or dictionary
- declaring: let tup: (i32, f64, u8) = (500, 2.4, 1);
- destructuring: let (x, y, z) = tup; println!("X= {}, Y= {}, Z={}", x, y, z);
- access by position: let a = tup.0; let b = tup.1;
- arrays
- collection of the SAME variable type
- arrays in rust are fix length! cant resize
- iteration: for element in months.iter() {
- tuples
-
functions
- main is the default function
- snake case lower case with underscores for naming
- return type indicated after the ->: fn sum(x:i32, y:i32) -> i32 {
- implicit return function is the final statement of the function WITHOUT SEMICOLON! fn sum(x:i32, y:i32) -> i32 { x + y }
- you can use an explicit return command
-
control flow
- if - standard
- loops
- loop { break; }
- while x != y, etc
- for element in arrayVar.iter() loop
-
memory management in rust
-
stack - stores values by order created in LIFO; data always appends, data lengths must be known, fixed and fast
- integers, booleans and literal strings (str) go here (Since the size of literals are fixed, they can be stored on the stack in a sequential manner.)
-
heap - unorganised, variable lengths, requires memory pointers
- compound/complex types go here, inc complex strings (String::from("hello"))
- Note: metadata for these variables are stored on the stack
-
rust has no garbage collector
-
rust uses concept of OWNERSHIP
-
rust calls drop to remove the variable from memory automatically when the var goes out of scope
-
the variable is the owner of the value
-
variables exist within scopes
-
each value can only be owned by one assignment at a time
-
when it exits the scope the value is destroyed
-
string types are memory allocated on the heap at run time
-
:: is used to advanced create function
-
let mut s = String::from("Hello"); < "Hello" is the initial value which allocates the first memory space
- s.push_str(", world!"); < appends by creating a new memory space
-
when out of scope it will invalidate the variable and remove it
-
for non simple data types (incl string), there are no default variable assignments by reference in rust! there is a shallow copy done.
- the first variable is dereferenced and no longer exists!!
-
let s2 = s1 < makes s1 no longer exist; the value in s1 MOVES to s2
-
if you pass s2 into a function as an argument it is then dereferenced in current scope!
- let s2 = 'foo'; func(s2); println!({},s2) < fails!
-
for complex types, you need to use explicit REFERENCES with "&" prepending the variable send and input
- this is a BORROW, allows to use a variable without taking ownership (assign by reference!)
let s = String::from("hello"); // s comes into scope. function_call(&s); fn_function_call(input_string: &String) { println!("{}",input_string); } println!("{}",s); < this now works!
- if you want to allow changing the value, you must append mut to all passes
- NOTE you can only have one mutation of a variable in a given scope
let mut s = String::from("hello"); // s comes into scope. function_call(&mut s); fn_function_call(input_string: &mut String) { println!("{}",input_string); } println!("{}",s); < this now works!
-
simple variables like int will be passed via COPY! beware
-
NOTE if you pass numeric by reference and want to mutate, you need to reference the variable with *!!! e.g. fn makes_copy(some_integer: &mut i32) { // some_integer comes into scope. *some_integer = *some_integer + 1;
-
race conditions
- remember! variables will be gone after the scope is complete (just good scoping practice...)
-
array or string slice
- slices is a range of values from the heap from the variable
- let s = String::from("hello"); slice = &s[0..3] > results in: "hel
-
struct is like class, creating a new data type, similar to tuples
-
unlike tuples you name the variables and define the types struct Rectangle{ width: u32, height: u32, }
-
structs can act like a class with methods
- stuct methods always have parameter of &self
impl Rectangle{ fn area(&self) -> u32{ self.width * self.height
} }
-
-
enumerations
-
like a struct but allows storage of types
-
only assigns ONE of the attributes, unlike a struct
-
can impl enums with methods
-
allows complex types
-
create a new type with possible values fn main() { enum IpAddressType { V4(String), V6(String) }
let home = IpAddressType::V4(String::from("127.0.0.1")); let loopback = IpAddressType::V6(String::from("::1"));
}
-
allows you to use strong typing to allow the enum root type "IpAddressType" only but then switch on the enumeration within
-
-
pattern matching
- powerful switch like function https://doc.rust-lang.org/book/ch06-02-match.html
-