class: center, middle # A Scala view of Rust cody@koeninger.org
https://koeninger.github.io/scala-view-of-rust I'm a Rust noob... take what I say with a grain of salt --- ## Scala = Java + ML * Need access to JVM libraries * But still want a sane modern language - static typing and inference - controlled mutability - algebraic data types - closures - pattern matching - parametric polymorphism / generics - ad hoc polymorphism / typeclasses - macros * OK with cost of garbage collection, latency, memory footprint --- ## Rust = C + ML * Need predictable latency / footprint, can't afford garbage collection * But still want a sane modern language - static typing and inference - controlled mutability - algebraic data types - closures - pattern matching - parametric polymorphism / generics - ad hoc polymorphism / typeclasses - macros * OK with cost of having to think more about memory - because the language helps you get it right, usually at **compile time** --- ## Stuff that Rust doesn't have (besides GC) * null pointer exceptions * data races * Tuple22 limit * catching exceptions * inheritance * higher kinded types * a good REPL --- ## Hello: Scala ```scala import scala.io.StdIn object Hello { def main(args: Array[String]) { println("What's your name?") StdIn.readLine() match { case name: String => println(s"Hello, $name") case _ => println("Sorry, I didn't hear your null pointer") } } } ``` --- ## Hello: Rust ```rust use std::io; fn main() { println!("What's your name?"); let mut name = String::new(); match io::stdin().read_line(&mut name) { Ok(_) => println!("Hello, {}", name), Err(e) => println!("Sorry, {}", e) } } ``` --- ## Building your code: Scala * install JVM + sbt * sbt run * sbt console * look for libraries on maven central or github, add them to build.sbt * sbt test (after deciding on a ~~religion~~ test framework) * sbt ~test * install JVM on your server * sbt assembly * copy fat jar to server and run it --- ## Building your code: Rust * install cargo * cargo run * feel sad about the lack of a repl :( * look for libraries on crates.io or github, add them to Cargo.toml * cargo test (using the simple built-in test framework) * cargo watch * check that build architecture matches server (or cross compile) * cargo build --release * copy statically linked binary to server and run it --- ## Null Pointers: Scala ```scala class Bad { def blowup(opt: Option[Int]) { opt.map(x => println(s"I got an $x")) } val x = Some(23) // scala would complain if this was a local variable instead var y: Option[Int] = _ blowup(x) // RUNTIME error: java.lang.NullPointerException blowup(y) } ``` --- ## Null Pointers: Rust ```rust fn no_blowup(opt: &Option
) { opt.map(|x| println!("I got an {:?}", x)); } fn main() { let x = Some(23); // COMPILE warning: single-assignment variable doesn't need to be mutable let mut y; no_blowup(&x); // COMPILE error: use of possibly uninitialized variable: `y` // no_blowup(&y); // COMPILE error: mismatched types // y = std::ptr::null(); // no_blowup(&y); y = None; no_blowup(&y); } ``` --- ## Ownership: Rust ```rust fn main() { let three = outer(); println!("Leaving main"); // Dropping three } fn outer() -> Entity { let one = Entity::new(1); let two = Entity::new(2); let three = Entity::new(3); inner(two); // COMPILE error: use of moved value //println!("this would be use-after-free {:?}", two); println!("Leaving Outer"); three // Dropping one } fn inner(e: Entity) { println!("Leaving Inner"); // Dropping two } ``` --- ## Borrowing references: Rust * two kinds of references - shared reference: **&** - mutable reference: **&mut** * two rules - a reference can't live longer than the thing it refers to - a mutable reference can't have an alias (another live reference) -- * enforced at compile time via subtyping of lifetime parameters: **'a** * 'a is a **subtype** of 'b if 'a outlives 'b, aka 'a is **bigger** in scope than 'b * lifetime parameters are usually inferred --- ## Reference that would live too long: Rust ```rust fn as_str(e: &Entity) -> &str { let s = format!("{:?}", e); // COMPILE error: s does not live long enough &s } ``` -- ``` // this isn't valid syntax, but an approximate desugaring fn as_str<'a>(e: &'a Entity) -> &'a str { 'b { let s = format!("{:?}", e); return &'a s } } ``` --- ## Aliased mutable reference: Rust ```rust let mut vector = Vec::with_capacity(1); vector.push(23); let head = &vector[0]; // COMPILE error: // cannot borrow vector as mutable because it is also borrowed as immutable vector.push(42); println!("head is {}", head); ``` -- ``` // this isn't valid syntax, but an approximate desugaring 'a { let mut vector = Vec::with_capacity(1); vector.push(23); 'b { let head: &'b i32 = Index::index::<'b>(&'b vector, 0) 'c { // COMPILE error: // cannot borrow vector as mutable because it is also borrowed as immutable Vec::push(&'c mut vector, 42); } println!("head is {}", head); } } ``` --- ## Borrow checker caveats: Rust * doesn't guarantee against leaks (but neither does GC) * when you need multiple mutable references, e.g. a graph, can either - use runtime reference counted types (Rc) and mutability checks (RefCell) - manage raw pointers yourself, encapsulated in unsafe { } blocks --- ## Data races: Scala ```scala class CountEvensVsOdds(reporter: ActorRef) extends Actor { val state = Array(0, 0) def receive = { case x: Int => if (x % 2 == 0) { state(0) = state(0) + 1 } else { state(1) = state(1) + 1 } if ((state(0) + state(1)) % 10 == 0) { // RUNTIME error: sharing mutable state between threads reporter ! state } } ``` --- ## Data races: Rust ```rust fn count_evens_vs_odds(rx: Receiver
, tx: Sender<&Vec
>) { let mut state = vec![0, 0]; loop { let x = rx.recv().unwrap(); if x % 2 == 0 { state[0] += 1; } else { state[1] += 1; } if (state[0] + state[1]) % 10 == 0 { // COMPILE error: state does not live long enough tx.send(&state).unwrap(); } } } ``` --- ## Algebraic Data Types: Scala ```scala /** A product type */ case class Point( val x: Double, val y: Double ) { def distanceFromOrigin = { math.sqrt(math.pow(x, 2) + math.pow(y, 2)) } } /** A sum type */ sealed abstract class Option[+A] extends Product with Serializable { } case object None extends Option[Nothing] { def isEmpty = true } final case class Some[+A](x: A) extends Option[A] { def isEmpty = false } ``` --- ## Algebraic Data Types: Rust ```rust /// A product type pub struct Point { x: f64, y: f64 } impl Point { fn distance_from_origin(&self) -> f64 { (self.x.powi(2) + self.y.powi(2)).sqrt() } } /// A sum type pub enum Option
{ None, // This will be optimized to a null pointer Some(T) } impl
Option
{ #[inline] pub fn is_some(&self) -> bool { match *self { Some(_) => true, None => false, } } } ``` --- ## Typeclasses: Scala ```scala object Json { trait ToJson[T] { def toJsonStr(t: T): String } implicit object IntToJson extends ToJson[Int] { def toJsonStr(i: Int) = i.toString } implicit def ArrayToJson[S: ToJson] = new ToJson[Array[S]] { def toJsonStr(a: Array[S]) = { a.map(x => implicitly[ToJson[S]].toJsonStr(x)). mkString("[", ", ", "]") } } implicit class ToJsonSyntax[S: ToJson](s: S) { def toJsonStr = implicitly[ToJson[S]].toJsonStr(s) } } object TypeclassExample { import Json._ def main(args: Array[String]) { val v = Array(1, 2, 3, 4) println(v.toJsonStr) } } ``` --- ## Typeclasses: Rust ```rust trait ToJson { fn to_json_str(&self) -> String; } impl ToJson for i32 { fn to_json_str(&self) -> String { self.to_string() } } impl
ToJson for Vec
{ fn to_json_str(&self) -> String { format!("[{}]", self.iter().map(|x| x.to_json_str()). collect::
>().join(", ")) } } fn main() { let v = vec![1, 2, 3, 4]; println!("{}", v.to_json_str()); } ``` --- ## Newtype: Scala ```scala case class Inches(self: Int) extends AnyVal { def +(other: Inches): Inches = { Inches(self + other.self) } } case class Feet(self: Int) extends AnyVal { def +(other: Feet): Feet = { Feet(self + other.self) } } def main(args: Array[String]) { val i = Inches(1) val i2 = Inches(2) val f = Feet(1) val f2 = Feet(2) println(s"f + f2 = ${f + f2}") println(s"i + i2 = ${i + i2}") // COMPILE error: type mismatch //println(s"f + i = ${f + i}") } ``` --- ## Newtype: Rust ```rust #[derive(Debug)] struct Inches(i32); impl Add for Inches { type Output = Inches; fn add(self, other: Inches) -> Inches { Inches(self.0 + other.0) } } #[derive(Debug)] struct Feet(i32); impl Add for Feet { type Output = Feet; fn add(self, other: Feet) -> Feet { Feet(self.0 + other.0) } } fn main() { let i = Inches(1); let i2 = Inches(2); let f = Feet(1); let f2 = Feet(2); println!("f + f2 = {:?}", f + f2); println!("i + i2 = {:?}", i + i2); // COMPILE error: mismatched types //println!("f + i = {:?}", f + i); } ``` --- ## Macros: Rust ```rust macro_rules! newtype_number { ($i:ident $t:ty) => { #[derive(Debug)] struct $i($t); impl Add for $i { type Output = $i; fn add(self, other: $i) -> $i { $i(self.0 + other.0) } } } } newtype_number!(Inches i32); newtype_number!(Feet i32); fn main() { let i = Inches(1); let i2 = Inches(2); let f = Feet(1); let f2 = Feet(2); println!("f + f2 = {:?}", f + f2); println!("i + i2 = {:?}", i + i2); } ``` --- ## Resources * [Code for these slides](https://github.com/koeninger/scala-view-of-rust) * [The Rust Book](http://doc.rust-lang.org/book/) * [Rust 101](http://www.ralfj.de/projects/rust-101/main.html) * [Learning Rust With Entirely Too Many Linked Lists](http://cglab.ca/~abeinges/blah/too-many-lists/book) * [The Rustonomicon](http://doc.rust-lang.org/nomicon/)