Created by Derek J. Goddeau and contributors
What properties do we need in a programming language for Robotics?
Essentially the same as a systems programming language.
Memory Safety | Minimal Runtime | Strong Type System | Performance | |
---|---|---|---|---|
C | ✔ | ✔ | ||
C++ | ✔ | ✔ | ||
C# | ✔ | ✔ | ||
D | ✔ | ✔ | ✔ | |
Go | ✔ | ✔ | ||
Nim | ✔ | ✔ | ✔ | |
Rust | ✔ | ✔ | ✔ | ✔ |
Java | ✔ | ✔ | ✔ | |
Python | ✔ | |||
Haskell | ✔ | ✔ |
I call it my billion-dollar mistake…
At that time, I was designing the first comprehensive type system for references in an object-oriented language. My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.
- Tony Hoare, inventor of ALGOL W.
No matter what, Safe Rust can't cause Undefined Behavior.
Sometimes we need to use unsafe.
fn index(idx: usize, arr: &[u8]) -> Option {
if idx < arr.len() {
unsafe {
Some(*arr.get_unchecked(idx))
}
} else {
None
}
}
*foo
is always ok
Dereferences never fail, and point to values of correct type.
(at compile time)
Linear types: Single Owner
let x = vec![1, 2, 3];
let y = x;
let z = x; // Value used after move
f(x);
f(x); // Value used after move
fn m(self) {...};
x.m();
x.m(); // Value used after move
Borrowing: Restricted Aliasing
let mut x = vec![1, 2, 3];
let y = &x;
let z = x; // Cannot move out of `x` because it is borrowed
let y = &x;
let z = &x;
let y = &mut x;
let z = &x; // Cannot borrow `x` as immutable because it is also borrowed as mutable
fn m(&mut self) {...}
Retrun an owned Vec
fn scope() -> Vec {
vec![1, 2, 4]
}
Restricted aliasing
fn scope2() {
let v1 = vec![1, 2, 3];
let v2 = v1; // Ownership has transfered, can't reference `v1`
v1.push(4); // Use of moved value: `v1`
v2
}
Clone to get a "deep copy"
fn scope3() {
let v1 = vec![1, 2, 3];
let v2 = v1.clone();
v1.push(4);
v2
}
Borrow to avoid the performance hit
fn scope4() {
fn foo(v1: &Vec) {
for v in v1 {
println!("{}", v);
}
}
let v1 = vec![1, 2, 3];
foo(&v1); // v1 is still valid
v1 // Ownership has been returned
}
Fearless Concurrency
Ownership is transfered to the new threads with the move
keyword
fn thread() {
let v = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("Here's a vector: {:?}", v)
});
handle.join().ok();
}
fn compute(input: &u32, output: &mut u32) {
if *input > 10 {
*output = 1;
}
if *input > 5 {
*output *= 2;
}
}
fn compute(input: &u32, output: &mut u32) {
let cached_input = *input; // keep *input in a register
if cached_input > 10 {
*output = 2; // x > 10 implies x > 5, so double and exit immediately
} else if cached_input > 5 {
*output *= 2;
}
}
// input == output == 0xabad1dea
// *input == *output == 20
if *input > 10 { // true (*input == 20)
*output = 1; // also overwrites *input, because they are the same
}
if *input > 5 { // false (*input == 1)
*output *= 2;
}
// *input == *output == 1
In Rust we know this input is impossible because &mut
isn't allowed to be aliased.
cargo build
It was a revelation that I could actually write an operating system and docargo build
and it would build my operating system.
-- Sergio Benitez
Cargo does unit testing and integration testing.
Integration tests go in a tests
directory and are written just as if the code was using the library.
cargo test
& cargo doc
/// Calculate the n-th Fibonacci number using Binet's formula.
///
/// # Example
///
/// ```
/// let fib = nth_fib(20);
/// ```
pub fn nth_fib(n: i32) -> i64 {
let nth = (PHI.powi(n) - PSI.powi(n)) / 5f64.sqrt();
(nth.round() as i64)
}
#[cfg(test)]
mod test {
use super::nth_fib;
#[test]
fn zero_to_five() {
assert_eq!(nth_fib(0), 0);
assert_eq!(nth_fib(1), 1);
assert_eq!(nth_fib(2), 1);
assert_eq!(nth_fib(3), 2);
assert_eq!(nth_fib(4), 3);
assert_eq!(nth_fib(5), 5);
}
}
cargo bench
#![feature(test)]
extern crate test;
pub fn add_two(a: i32) -> i32 {
a + 2
}
#[cfg(test)]
mod tests {
use super::*;
use test::Bencher;
#[test]
fn it_works() {
assert_eq!(4, add_two(2));
}
#[bench]
fn bench_add_two(b: &mut Bencher) {
b.iter(|| add_two(2));
}
}
cargo run --example my_example_name
The one true style.
Compiler errors that are actually useful!
fn main() {
let s1 = String::from("Hello");
let s2 = s1;
println!("{} World!", s1)
}
error[E0382]: use of moved value: `s1` --> src/main.rs:5:27 | 3 | let s2 = s1; | -- value moved here 4 | 5 | println!("{} World!", s1); | ^^ value used here after move | = note: move occurs because `s1` has type `std::string::String`, which does not implement the `Copy` trait
For types that are nearly costless to deep copy like i64
implementing the Copy
trait will allow this to work.
fn main() {
let s = String::from("hello");
change(&s);
}
fn change(some_string: &String) {
some_string.push_str(", world");
}
error[E0596]: cannot borrow immutable borrowed content `*some_string` as mutable --> src/main.rs:8:5 | 7 | fn change(some_string: &String) { | ------- use `&mut String` here to make mutable 8 | some_string.push_str(", world"); | error[E0596]^^^^^^^^^^^ cannot borrow as mutable