Rust: Difference between revisions
Line 834: | Line 834: | ||
To do this we specify annotations. The extension in vscode does this for us but clearly it is important we understand. But note, they are only annotation and do not change the lifetime of anything only assist the compiler. | To do this we specify annotations. The extension in vscode does this for us but clearly it is important we understand. But note, they are only annotation and do not change the lifetime of anything only assist the compiler. | ||
<syntaxhighlight lang="rust"> | <syntaxhighlight lang="rust"> | ||
fn main() { | |||
println!("Hello, world!"); | |||
let string1: String = String::from("abcd"); | |||
let string2: String = String::from("xyz"); | |||
let result = longest(&string1, &string2); | |||
println!("The longest string is {}", result) | |||
} | |||
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { | |||
if x.len() > y.len() { | |||
x | |||
} | |||
else { | |||
y | |||
} | |||
} | |||
</syntaxhighlight> | |||
Lets make a dangling reference | |||
<syntaxhighlight lang="rust"> | |||
fn main() { | |||
println!("Hello, world!"); | |||
let string1: String = String::from("abcd"); | |||
let result: &str; | |||
{ | |||
let string2: String = String::from("xyz"); | |||
result = longest(&string1, &string2); | |||
} | |||
println!("The longest string is {}", result) | |||
} | |||
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { | ||
if x.len() > y.len() { | if x.len() > y.len() { | ||
Line 842: | Line 871: | ||
} | } | ||
} | } | ||
</syntaxhighlight> | |||
This fails because the string2 does not have a long enough life and is highlighted in the editor and compiler | |||
<syntaxhighlight lang="rust"> | |||
| | |||
6 | let string2: String = String::from("xyz"); | |||
| ------- binding `string2` declared here | |||
7 | result = longest(&string1, &string2); | |||
| ^^^^^^^^ borrowed value does not live long enough | |||
8 | } | |||
| - `string2` dropped here while still borrowed | |||
9 | println!("The longest string is {}", result) | |||
| ------ borrow later used here | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Revision as of 06:17, 30 May 2023
Terms
- Fat Pointer Contains Address actual data and length
Sample program
fn main() {
println!("Hello, world!");
}
Cargo
Sample file
[package]
name = "hello_world"
version = "0.0.1"
authors = [ "Iain Wiseman iwiseman@bibble.co.nz" ]
Sample commands
cargo new hello_world --bin
cargo build
cargo run
Types and Variables
Fundamental Data Types
Primitive types
Cam declare with size of type
let a:u8 = 123; // unsigned int 8 bits number immutable
let a:i8 = 123; // signed int 8 bits number immutable
let mut a:u8 = 123; // unsigned int 8 bits number mutable
Or without e.g.
let mut c = 123456789 // 32-bit signed i32
println!("c = {}", c);
Now variable based on OS e.g.
let z:isize = 123 // signed 64 bit if on 64 bit OS
Decimal
let e:f64 = 2.5 // double-precision, 8 bytes or 64-bits
Char
let x:char = 'x' // Note 4 bytes unicode
boolean
let g:bool = false; // Note 4 bytes unicode
Operators
Does not support -- and ++ but does support
a -= 2;
Remainder can be calculated using
a%3
Bitwise
let c = 1 | 2 // | OR
Shift
let two_to_10 = 1 << 10; // 1024
Logical of standard e.g.
let pi_less_4 = std::f64::consts::PI < 4.0; // true
Scope and shadowing
Curly braces keep scope
fn test()
{
{
let a = 5;
}
println!("Broken {a}");
}
Shadowing is fine though
fn test()
{
let a = 5;
{
let a = 10;
println!("10 {a}");
}
println!("5 {a}");
}
Constants
Standard const
const MEANING_OF_LIFE:u8 = 42;
Static const
static Z:i32 = 123;
Stack and Heap
Same a c++ i.e.
let y = Box::new(10);
println!("y = {}", *y);
Control Flow
if statement
Same as C++ except no brackets
if temp > 30
{
println!("Blah");
}
else if temp < 10
{
println!("Blah");
}
else
{
println!("Blah");
}
Elvis is like
let a = if temp > 30 {"sunny"} else {"cloud"}
While and Loop
While
Same as C++ except no brackets
while x < 1000
{
}
There is support for continue and break
Loop
Loop is while true
loop
{
if y == 1 << 10 { break; }
}
For Loop
A bit like kotlin loops (I think)
for x in 1..11
{
println!("x = {}",x);
}
You can get position in series as well
for (pos,x) in (1..11).enumerate()
{
println!("x = {}, pos = {}",x, pos);
}
Match
Match can be used like case
let country = match country_code
{
44 => "uk",
46 => "sweden",
7 => "russia"
1...999 => "unknown" // other triple dot inclusive
_ => "invalid" // invalid
};
Data Structures
Structs
There are 3 types of structs, name, tuple and unit structs
Name Struct
struct User
{
active: bool,
username: String,
sign_in_count: u32
}
let user1 = User{active: true, username: String::from("Biil"),
sign_in_count: 0};
println!("{}", user1.username);
...
fn build_user(username: String) -> User {
User {
username,
active:true,
sign_in_count: 1
}
}
Tuple Struct
Tuple structs use the order in which declared to assign.
struct Coordinates{i32,i32,i32};
let coords = Coordinates{1,2,3};
Unit Struct
These are used to mark the existence of something
struct UnitStruct;
let a = UnitStruct{}
The example shown was when you are implementing a trait (interface) but the properties were not required for this type. So given a trait for Area, Square uses size but Point does not have an area as it is zero
trait AreaCalculator {
fn calc_area(&self) => f64
}
struct Square {
size: f64
}
struct Point;
impl AreaCalculator for Square {
fn calc_area(&self) -> f64 {
self.size * self.size
}
}
impl AreaCalculator for Point {
fn calc_area(&self) -> f64 {
0.0
}
}
We can use it for error
struct DivideByZero;
fn divide(nom: f64, den: f64) -> Result<f64, DivideByZero> {
if den != 0.0 {
Ok(nom/den)
} else {
Err(DivideByZero)
}
}
Other Examples
struct Point
{
x: f64,
y: f64
}
fn main()
{
let p = Point { x: 30.0, y: 4.0 };
println!("point is at ({},{})", p.x, p.y)
}
Enumerators
Similar to c++
enum Color {
Red,
Green,
Blue
}
fn main()
{
let c:Color = Color::Red;
match c
{
Color::Red => prinln!("Color is Red");
Color::Green => prinln!("Color is Green");
}
}
But maybe not we can add types
enum Color {
Red,
Green,
Blue,
RgbColor(u8,u8,u8) // Tuple
CmykColor{cyan:u8, magenta:u8, yellow:u8, black:u8,} // Struct
}
fn main()
{
let c:Color = Color::RgbColor(10,0.0);
match c
{
Color::Red => prinln!("Color is Red");
Color::Green => prinln!("Color is Green");
Color::RgbColor(0,0,0) => prinln!("Color is Black");
Color::RgbColor(r,g,b) => prinln!("Color is {},{},{}", r,g,b);
}
let d:Color = Color::CmykColor(cyan:0, magenta:0, yellow:0, black:0);
match d
{
Color::Red => prinln!("Color is Red");
Color::Green => prinln!("Color is Green");
Color::RgbColor(0,0,0) => prinln!("Color is Black");
Color::CmykColor(cyan:_, magenta:_, yellow:_, black:255) => prinln!("Black");
}
}
Option <T> and if let
Used to avoid null or invalid values
let x = 3.0
let y = 0.0 // Divide by zero
let result:Option<f64> =
if y != 0.0 { Some(x/y) } else { None };
// Using match
match result
{
Some(z) => println!("Goody result"),
None => println!("No result)
}
// Using if let
if let Some(z) = result { println!("z = {}", z); }
Arrays
Array sizes cannot grow in rust
Simple
let mut a:[i32;5] = [1,2,3,4,5];
// Or
let mut a = [1,2,3,4,5];
// Length
a.len()
// Assignment
a[0] = 321
// Printing
println!("{:?}", )
// Testing
if a == [1,2,3,4,5]
{
}
// Initialise
let b = [1,10]; // 10 array initialised to 1
Multi Dimension
Here is a two dimension array
let mtx:[[f32;3];2] =
[
[1.0, 0.0, 0.0],
[0.0, 2.0, 0.0],
];
Vectors
Same a c++
let mut a = Vec::new()
a.push(1);
a.push(2);
a.push(3);
// Print
println!("a[0] {}", a[0]);
// We can create vector with initial capacity
let mut b = Vec::<i32>::with_capacity(2);
// We can initialize using an iterator values of 0-4
let c: Vect<i32> = (0..5).collect();
// Using get returns a option
match a.get(3333)
{
...
}
// Removing, pop returns an option
let last_elem = a.pop();
// Using the option type iterating over vector to print it
while let Some(x) = a.pop()
{
println!("x = {}",x);
}
More Data Structures
Slices
A slice is a non-owning pointer to a block of memory. For example
// Create a vector
let v: Vec<i32> = {0..5}.collect();
// Now create a slice (reference)
let sv: &[i32]= &v;
// We create a slice with only some elements
let sv1: &[i32]= &v[2..4];
// Printing these will produce the same result
println!("{:?}",v);
println!("{:?}",sv);
// And the range
println!("{:?}",sv1);
Get the first 3 elements of an array
fn use_slice(slice: &mut[i32])
{
}
fn test()
{
let mut data = [1,2,3,4,5];
// Passes element 1-3 to use_slice as a reference
use_slice( &mut data[1..4]);
}
Strings
Basic String
let name = String::from("Iain");
Two types, static string and string type
let s = "hello";
// Cannot do
// let h = s[0]
// You can iterate as a sequence using chars e.g.
for c in s.chars()
{
println!("{}", c);
}
And now the mutable string in rust essentially an vector // Create a string
let mut letters = String::new();
Add a char
let a = 'a' as u8;
letters.push(a as char);
String to str
let u:%str = &letters;
Concatenation
let z = lettters + &letters
Other examples
let mut abc = "hello world".to_string()'
abc.remove(0);
abc.push_str("!!!");
abc.replace("ello","goodbye")
Tuples
Eezy peezy lemon squeezy
fn sum_and_product(x:i32,y:i32) -> (i32, i32)
{
(x+y, x*y)
}
fn main()
{
let sp = sum_and_product(3,4);
let (a,b) = sp;
let sp2 = sum_and_product(4,5);
// combine
let combined = (sp, sp2);
let ((c,d), (e,f)) = combined;
}
Pattern Matching
Various pattern matching
match x
{
0 => "zero"
1 | 2 => "one or two"
9...11 => "lots of"
_ if(blahh) => "something"
_ => "all others"
}
Using two dots .. with pattern matching allows you to ignore other values except those specified.
Generics
Looks like templates
struct Point<T>
{
x: T,
y: T
}
fn generics()
{
let a:Point<i32> = Point {x: 0, y: 4}
}
Functions
Functions and Arguments
No surprises
fn print_value(x: i32)
{
println("value = {}", x);
}
Pass by reference
fn increase(x: &mut i32)
{
*x = 1;
}
Return value, note no semicolon
fn product(x: i32, y: i32) -> i32
{
x * y
}
Return two values, note no semicolon
fn product(x: i32) -> i32
{
if x == 10 {
123
}
321
}
Methods on Structs
Add method len to struct
struct Line
{
start: Point,
end: Point
}
// Declare impl using the keyword impl. Not ends with no semi colon.
impl Line
{
fn len(&self) -> f64
{
let dx = self.start.x - self.end.x;
let dy = self.start.y - self.end.y;
(dx*dx+dy*dy).sqrt()
}
}
Closures
Closures are delegates. Here is an inline one
let plus_one = |x:i32| -> i32 { x + 1};
let a = 6;
println!("{}, + 1 = {}",a plus_one(a));
// another approach
let plus_to = |x|
{
let mut z = x;
z += 2;
z
};
We need to ensure scope is executed before using variables again e.g.
let mut two = 2;
let plus_to = |x|
{
let mut z = x;
z += two;
z
};
let borrow_two = &mut two;
This will not compile as two could be modified so you need to add bracket to ensure scope.
let mut two = 2;
{
let plus_to = |x|
{
let mut z = x;
z += two;
z
};
}
let borrow_two = &mut two;
Higher-order functions
Not sure what this is, seems like just a way to chain written functions together like lamba. Here is the given example.
fn is_even(x: u32) -> bool
{
x%2 == 0
}
fn main()
{
// Method without HOF
let limit = 500;
let mut sum = 0;
for i in 0..
{
let isq = i*i;
if isq > limit { break; }
else if is_even(isq) { sum += isq; }
}
println!("loop sum = {}", sum);
// HOF way
let sum2 =
(0...).map(|x| x*x)
.take_while(|&x| x < limit)
.filter(|x| is_even(*x))
.fold(0, |sum,x| sum+x);
println!("hof sum = {}", sum2);
}
Traits
Traits are interfaces from maybe c#
trait Animal
{
fn create(name:&'static str);
fn name(&self) => &'static str;
fn talk(&self)
{
println!("{} cannot talk",self.name());
}
}
struct Human
{
name: &'static str;
}
// Implement interface
impl Animal for Human
{
fn create(name:&'static str) -> Human
{
Human{name: name}
}
fn name(&self) -> &'static str
{
self.name
}
// override default
fn talk(&self)
{
println!("{} can talk",self.name());
}
}
struct Cat
{
name: &'static str;
}
// Implement interface
impl Animal for Cat
{
fn create(name:&'static str) -> Cat
{
Cat{name: name}
}
fn name(&self) -> &'static str
{
self.name
}
// override default
fn talk(&self)
{
println!("{} says meeow",self.name());
}
}
// Create doing
let h:Human = Animal::create("John");
let c:Cat = Animal::create("John");
Lifetime
Ownership
Only one thing can have ownership
let v = vec![1,2,3]
let v2 = v;
println!("{:?}",v)
Only v can own the vector so this will not compile. Upon usage you get the error "use of moved value"
Move
If we change the code to use v2 instead. This is OK because we have successfully moved the ownership to v2
let v = vec![1,2,3]
let v2 = v;
println!("{:?}",v2)
Clone
This is a way to duplicate the value to fix this. Obviously this has a cost
let v = vec![1,2,3]
let v2 = v.cone();
println!("{:?}",v)
println!("{:?}",v2)
Copy
Primitive types and others can implement copy which would allow the automatic duplication of a type. Here is i32.
let v = 1
let v2 = v
println!("{:?}",v)
println!("{:?}",v2)
References
We can pass references to an object using mut to modify a string.
...
let mut s = String::from("hello");
change_string(&mut s);
printlin!("{}",s)
}
fn change_string(some_string: &mut String) {
some_thing.push_str(", world!");
}
Borrowing
This fails because we are have two mutable accesses to a
let mut a = 40;
let b &mut a
*b += 2;
println!("a = {}", a);
This fails because we are have two mutable accesses to a
Same fix as before
let mut a = 40;
{
let b &mut a
*b += 2;
}
println!("a = {}", a);
Dangling References
We cannot do the code below as r refers to something which has gone out of scope
let r;
{
let x = 5;
r = &x;
}
println!("{}",r);
Lifetime Annotations
This was a bit confusing but here goes. Here is a function without lifetime annotations. This fails to compile as we need to let the compiler know how long the return value will live for.
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
}
else {
y
}
}
To do this we specify annotations. The extension in vscode does this for us but clearly it is important we understand. But note, they are only annotation and do not change the lifetime of anything only assist the compiler.
fn main() {
println!("Hello, world!");
let string1: String = String::from("abcd");
let string2: String = String::from("xyz");
let result = longest(&string1, &string2);
println!("The longest string is {}", result)
}
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
}
else {
y
}
}
Lets make a dangling reference
fn main() {
println!("Hello, world!");
let string1: String = String::from("abcd");
let result: &str;
{
let string2: String = String::from("xyz");
result = longest(&string1, &string2);
}
println!("The longest string is {}", result)
}
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
}
else {
y
}
}
This fails because the string2 does not have a long enough life and is highlighted in the editor and compiler
|
6 | let string2: String = String::from("xyz");
| ------- binding `string2` declared here
7 | result = longest(&string1, &string2);
| ^^^^^^^^ borrowed value does not live long enough
8 | }
| - `string2` dropped here while still borrowed
9 | println!("The longest string is {}", result)
| ------ borrow later used here
Odds and Ends
Consuming Crates
Crates is like nuget [1]
[package]
name = "mypackage"
version = "0.1.0"
author = " ["Iain Wiseman iwiseman@bibble.co.nz"]
[dependencies]
rand = "0.3.12"
And the usage
extern crate rand; // Package
use rand::Rng; // Namespace
fn main() {
let mut rng = rand::thread_rng();
let b:bool = rng.gen();
}
Building Crates and Modules
Module example e.g. src/lib.rs
pub mod greetings
{
pub mod english
{
pub fn hello() -> {"hello"->to_string() }
pub fn goodbye() -> {"goodbye"->to_string() }
}
pub mod french
{
pub fn hello() -> {"bonjour"->to_string() }
pub fn goodbye() -> {"au revoir"->to_string() }
}
}
For modules within modules you can make directories. For above this would be greetings\english and greetings\french. So we could have greetings\english\english.rs
pub fn hello() -> {"hello"->to_string() }
pub fn goodbye() -> {"goodbye"->to_string() }
greetings\lib.rs
pub mod greetings
{
pub mod english;
pub mod french
{
pub fn hello() -> {"bonjour"->to_string() }
pub fn goodbye() -> {"au revoir"->to_string() }
}
}
We need a package file for it
[package]
name = "phrases"
version = "0.1.0"
author = " ["Iain Wiseman iwiseman@bibble.co.nz"]
And then cargo build should work. To use the package you will need to specify the path to the package in the cargo file where used. e.g.
[package]
name = "mypackage"
version = "0.1.0"
author = " ["Iain Wiseman iwiseman@bibble.co.nz"]
[dependencies]
rand = "0.3.12"
phrases = { path = "../Phrases" }
Testing
Rush comes with it's own assert library marked by #[test] e.g. for the above
#[test]
fn english_greeting_correct()
{
assert_eq!("hello", greetings::english::hello());
}
Documentation
rustdoc is used to generate.
//! This module comment
//! and this
//! #Examples are compiled with back ticks
//! ```
//! let username = "John";
//! println!("{}", english::hello());
//! ```
/// This is for code
/// In this case, it's our `hello()` function.
pub fn hello() -> String {" hello".to_string() }
Installing
Do not use apt as it does not set rustup correctly and then vscode extension will not work with "Couldn't start client Rust Language Server"
apt-get install curl build-essential make gcc -y
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
VS Code setup
I ended up installing. Still cannot find a in built package manager for cargo
Date Handling
Found the on twitter (not all bad) @orhanbalci Type conversion and dates are the worst in most languages so thought I would keep this.
fn main() {
// you can use chrono crate for date time operations.
// chrono crate is more capable than standard library std::time module
use chrono::prelude::*;
use chrono::Duration;
// you can retrieve current date in Utc timezone as follows
let current_date = Utc::today();
println!("Utc current date: {}", current_date);
// you can retrieve current date in local timezone as follows
let local_current_date = Local::today();
println!("Local current date: {}", local_current_date);
// you can retrieve current time in UTC as follows
let current_time_utc = Utc::now();
println!( "Utc current time: {}", current_time_utc);
// you can retrieve current time in local time zone as follows
let current_time_local = Local::now();
println!("Local current time: {}", current_time_local);
// you can add some duration to a chrono::Date
// succ method gets succeeding date
let today = Utc::today();
let tomorrow = today + Duration::days(1);
assert_eq!(today.succ(), tomorrow);
// you can subtract some duration from a chrono
// pred method gets previous date
let today = Utc::today();
let yesterday = today - Duration::days(1);
assert_eq!(today.pred(), yesterday);
// you can get UNIX timestamp (epoch) value for a datetime using
// timestamp method of chrono::offset::TimeZone trait.
// since timestamps have numeric representation, they are easy to store in db
// and send through network. You can aldo prefer this notation in your APIs
let dt = Utc.ymd(1970, 1, 1).and_hms_milli(0, 0, 1, 0);
assert_eq!(dt.timestamp(), 1);
//you can also get timestamp value of a datetime in milliseconds
let dt = Utc.ymd(1970, 1, 1).and_hms_milli(0, 0, 1, 500);
assert_eq!(dt.timestamp_millis(), 1500);
//you can convert create a chrono::DateTime from a timestamp seconds
let timestamp = 15;
let datetime = Utc.timestamp(timestamp, 0);
assert_eq!(datetime.timestamp(), 15);
//you can get difference of two date times as follows
let first = Utc.ymd(1970, 1, 1).and_hms_milli(0, 0, 1, 0);
let second = Utc.ymd(1970, 1, 1).and_hms_milli(0, 0, 2, 0);
let difference: Duration = second.signed_duration_since(first);
assert_eq!(difference, Duration::seconds(1));
// you can also add and subtract duration from a DateTime struct
let now = Utc::now();
let yesterday_at_the_same_time = now - Duration::days(1);
println!("Yesterday at the same time: {}", yesterday_at_the_same_time);
//you can compare durations
assert_eq! (Duration::days(1), Duration::hours(24));
//you can format your DateTime struct using strftime formatting options
let now = Utc::now();
println!("{}", now. format( "%d.%m.%Y %H:%M"));
//you can parse DateTime from a formatted string as below
let some_time = NaiveDateTime::parse_from_str("01.10.2021 14:21", "%d.%m.%Y %H:%M").unwrap( );
assert_eq!(some_time, NaiveDate::from_ymd(2021,10,1).and_hms(14,21,0));
}