Just some stuff about me.
Here's my dotfiles repository.
What links here:
rustc
is compiler, cargo
is build tool, rustfmt
is formatter. Execution starts from main
function.
To turn off compilation warnings (only temporarily, otherwise keep them on!):
#[allow(unused_variables, dead_code, ...)]
mutable have to be indicated with mut
, types can be inferred and can be suffixed to numbers:
let x: i32 = 1i32; // immutable
let mut mutable = 1; // mutable, inferred type
const MYCONST = 0x42; // compile-time constant
static MYGLOB: u32 = 0x42; // global variable
// Creating Strings from &str
let s: &str = "string literal";
let s: String = "heap-allocated string".to_string();
// Convert String to &str:
let s_ref: &str = s.as_str();
let s_ref: &str = &*s;
println!("{} {:?}", s, s); // with `:?`, print debug-style
let fixed_size: [i32; 4] = [1, 2, 3, 4];
let mut dynamic_size: Vec<i32> = vec![5, 6, 7, 8];
dynamic_size.push(fixed_size[1]); // can add elements like this, indexing with [] from 0
// Mapping over an array
let list = [1,2,3];
let result: Vec<i64> = list.iter().map(|x| x*2).collect(); // without collect(), returns iterator. then you can run .next(), .all(), etc.
let s = "Hello".to_string();
let mut dynamic_size: Vec<i32> = vec![5, 6, 7, 8];
let s_slice: &str = &s[..]; // string slices are immutable 'views' into another string
let arr_slice: &[i32] = &dynamic_size[0..2]; // same for array slices, inclusive with [0..=2]
let x: (I32, &str, f64) = (1, "answer", 42.69);
let (a, b, c) = x; // you can destructure like this
println!("{}", x.1); // and index with ., zero-based
// named fields
struct Point {
x: i32,
y: i32
}
// unnamed fields ("tuple struct")
struct Point2(i32, i32);
// generic struct
struct Foo<T> { bar: T }
// initializing
let origin: Point = Point { x: 0, y: 0 };
let origin2: Point2(0, 0);
let foo = Foo { bar: "hello".to_string() };
// accessing
println!("{} {} {}", origin.x, origin.y, foo.bar);
// C-like
enum Direction { Left, Right, Up, Down }
let up = Direction::Up;
// Containing values
enum OptionalI32 { Some(i32), None }
let two = OptionalI32::Some(2);
let nothing = OptionalI32::None;
// Generic
enum SMLOption<T> { Some(T), None }
let maybestr: Option<&str> = Some("hello");
let nonumber: Option<u32> = None;
assert!(maybestr.is_some() && nonumber.is_none());
println!("str is {}", maybestr.unwrap());
println!("nonumber will not print here {}", nonumber.expect("Can't unwrap a None.")); // this panics
// One way to access:
if let Some(s) = maybestr {
dbg!(s);
}
// You can also use pattern matching, see below.
You can define a type alias like in C:
type MyType = u32;
fn add(a: i32, b: i32) -> i32 {
a + b // no semicolon means implicit return
}
fn main() {
println!("{}", add(2, 3));
}
pub struct Foo { val: i32 }
// can also be generic, then you do `impl<T> Foo<T>`
impl Foo {
// constructor
pub fn new() -> Self {
Foo { val: 0 }
}
// inherent function, called with `::`
pub fn sayhello() {
println!("Hello");
}
// private method
fn set_even(&mut self, val: i32) {
self.val = val;
}
// public setter method
pub fn set(&mut self, val: i32) {
if val % 2 == 0 {
self.set_even(val);
}
else {
eprintln!("{} not even, not setting", val);
}
}
// public gtter method
pub fn get(&self) -> i32 {
self.val
}
// consume: move the value into the function to prevent reuse in following code
pub fn consume(self) -> i32 {
self.val
}
}
Foo::sayhello();
let foo = Foo::new();
foo.set(4);
dbg!(foo.consume());
dbg!(foo.get()); // Error: foo was moved in the consume call
each value has one owner. Simple types and tuples that only contain simple types implement Copy (i.e. if passed into function, the function doesn’t take ownership). For example:
fn copies(n: i32) {
println!("inside copies: {}", n);
}
fn takes_ownership(s: String) {
println!("inside: {}", s);
}
fn uses_reference(s: &String) {
println!("inside: {}", s);
s.push_str(", world"); // fails! reference is not mutable
}
fn uses_mut_reference(s: &mut String) {
s.push_str(", world");
}
fn main() {
let x = 42; // "x" owns the int value
copies(x); // value copied
println!("outside: {}", x); // OK, because i32 implements Copy
let s = "hello".to_string(); // "s" owns the string value
takes_ownership(s); // value moved here, owned inside function
println!("outside: {}", s); // "s" is now invalid! because borrowed after move -- function parameter has ownership.
let mut s2 = "hello".to_string(); // "s2" owns the string value
uses_reference(&s2); // the function borrows the value, but s2 retains ownership
println!("outside: {}", s2); // so this is valid
uses_mut_reference(&mut s2); // this lets us change the value
println!("after change: {}", s2);
// This fails. You can't have simultaneously in the same scope: multiple mutable refs, mutable + immutable refs.
let r1 = &mut s2;
let r2 = &mut s2;
// to fix, you can put r2 into its own scope with curly braces.
}
All values are stack-allocated by default. To allocate on the heap, use a Box:
let mut foo: Box<i32> = Box::new(3);
println!("{}", foo);
*foo = 42;
println!("{}", foo);
struct Foo {
a: i32,
b: i32
}
trait FooPredicates {
fn all_even(&self) -> bool;
fn some_odd(&self) -> bool;
}
impl FooPredicates for Foo {
fn all_even(&self) -> bool {
(self.a % 2 == 0) && (self.b % 2 == 0)
}
fn some_odd(&self) -> bool {
!self.all_even()
}
}
fn main() {
let mut f = Foo { a: 2, b: 4 };
println!("all_even {}, some_odd {}", f.all_even(), f.some_odd());
f.b = 3;
println!("all_even {}, some_odd {}", f.all_even(), f.some_odd());
f.a = 5;
println!("all_even {}, some_odd {}", f.all_even(), f.some_odd());
}
You can automatically implement traits with derive, e.g. #[derive(Copy)]
, if all members of the struct also implement the trait.
struct Foo {
a: i32,
b: Option<i32> // from stdlib
}
fn matcher(foo: &Foo) {
match foo {
Foo { a: n, b: Some(m) } if n == m => println!("both are {}", n),
Foo { a: _, b: Some(m) } => println!("there is some {}", m),
Foo { a: n, b: None } => println!("none, only a is {}", n)
}
}
fn main() {
let mut foo = Foo { a: 2, b: Some(42) };
matcher(&foo);
foo.b = None;
matcher(&foo);
foo.b = Some(foo.a);
matcher(&foo);
}
use std::fs;
fn main() {
match fs::read("nonexistentfile") {
Ok(_) => println!("This won't display."),
Err(e) => {
eprintln!("We know the file doesn't exist!");
panic!("{}", e);
}
}
}
// You can also not handle, with .unwrap(), .expect(error message), etc.
// .expect() gives you the error message when there's an error.
// Both panic in cases where an Option is None, or Result is Err!
// Using question mark either gives you the result, or returns an error.
// iterate over array
for i in &[1,2,3] {
println!("{}", i);
}
// iterate over range (exclusive highest number)
for i in 0..10 {
print!("{} ", i);
}; println!("");
// iterate with index
for (i, v) in (10..20).enumerate() {
println!("{}: {}", i, v);
}
// while loop
while 1 == 1 {
println!("everything ok");
break
}
// and `loop { ... }` is infinite
You can also break-with-value:
use rand::Rng;
fn main() {
let mut rng = rand::thread_rng();
let mut attempt = 0;
let res = loop {
attempt += 1;
let num: u32 = rng.gen();
if num % 2 == 0 {
break num;
}
};
println!("got even number: {} (attempt {})", res, attempt);
}
this blog post does a better job explaining than I could
// Add to Cargo.toml:
// [dependencies]
//
// Given a file like this called config.toml:
//
// [config]
// answer = 42
// other = [{name = "something"}]
//
use serde::Deserialize;
use std::fs;
// Set up config structure
#[derive(Deserialize)]
struct ConfigData {
config: Config
}
#[derive(Deserialize)]
struct Config {
answer: i32,
other: Vec<Item>
}
#[derive(Deserialize)]
struct Item {
name: String
}
fn main() {
// Read file (need to do error handling)
let contents = fs::read_to_string("config.toml").unwrap();
// Parse config (need to do error handling)
let data: ConfigData = toml::from_str(&contents).unwrap();
println!("answer is {}, and name is {}", data.config.answer, data.config.other[0].name);
}
use std::process::Command
// Interactive
let _ = Command::new("less")
.env("LESS", "")
.args(&["-RiX", "/etc/profile"])
.spawn()
.expect("Less didn't work.")
.wait();
// Output to /dev/null
Command::new("ls")
.arg("-l")
.stdout(std::process::Stdio::null())
.spawn()
.expect("ls couldn't ls.");
// Collect output
use std::io::{self,Write};
fn main() {
let output = Command::new("ls")
.arg("-l")
.output()
.expect("ls couldn't ls.");
println!("status: {}", output.status);
io::stdout().write_all(&output.stdout).unwrap();
}
// returns a mapped copy
fn mymap<T, F>(v: Vec<T>, f: F) -> Vec<T>
where
F: Fn(T) -> T,
{
let mut res = vec![];
for i in v {
res.push(f(i));
}
return res;
}
// modifies the existing vector in-place
fn mymapinplace<T, F>(v: &mut Vec<T>, f: F)
where
T: Copy,
F: Fn(T) -> T,
{
for i in v {
*i = f(*i)
}
}
fn main() {
let v = mymap(vec![1, 2, 3, 4], |x: i32| x * 2);
dbg!(&v);
let mut w = vec![2.42, 3.42, 4.42];
mymapinplace(&mut w, |x: f32| x.powf(2f32));
dbg!(&w);
}
You can also return functions:
fn returns_fn() -> impl Fn(i32) -> i32 {
|x| x*2
}
fn main() {
let x = 42;
let y = returns_fn()(x);
dbg!(y);
}