) -> Self::Result {
msg.0 + msg.1
}
}
```
???
Actix is an actor framework for Rust.
Notice how there are no classes in Rust, but what you do is implement different traits.
Here there are three diff traits for actors - Message Actor and Handler.
Also notice the `&mut self`. Every method declares if it needs mutable or immutable version of self.
This is why there are mutability checks everywhere.
---
# Safe Numeric Processing
* Rust does not coerce `Ints` to `Longs` automatically
* Explicit signed and unsigned types
* Scala: you have to pick shift operator (`>>` vs `>>>`) depending on signedness
* Opt-in correctness :(
* Rust: `u16 >>` is unsigned shift, `i16 >>` is a signed shift. Type and compiler enforced!
* Different methods for handling overflows
* `checked_add`
* `overflowing_add`
* `saturating_add`
* 128-bit primitive type is kinda cool
Some of this is of course possible in Scala too, via Spire for example.
---
class: center, middle
# Performance AND Abstrations
---
# Structs and Performance
```scala
final case class ChunkQueryInfo(info: ChunkSetInfo,
tsReader: LongDataReader, valueReader: VectorDataReader)
final case class ChunkSetInfo(id: Long, startTime: Long, endTime: Long,
numRows: Int, chunks: Seq[ChunkPointer])
val chunksToQuery: Seq[ChunkQueryInfo]
```
16 byte header per object:
graph LR;
CQI(16B:ChunkQueryInfo) --> CSI(16B:ChunkSetInfo)
CQI --> VDR(16B:VectorDataReader)
CSI --> Seq(16B:Seq of ChunkPointer)
Seq --> Item1[java.lang.Long]
Seq --> Item2[java.lang.Long]
For chunks length of 2, each `ChunkQueryInfo` has ~128 bytes overhead
???
Example1: Scala object graph, from FiloDB, bunch of case classes
Really big deal for data processing and databases.
Overhead maybe as big as data fields themselves
---
# Structs in Rust
```rust
struct ChunkQueryInfo {
info: ChunkSetInfo,
ts_reader: LongDataReader,
valueReader: VectorDataReader
}
struct ChunkSetInfo {
id: u64,
start_time: u64,
end_time: u64,
num_rows: u32,
chunks: Vec<&Chunk>
}
```
* Structs are inlined - no object header overhead
* 112 bytes saved per `ChunkQueryInfo`
* Returning structs on stack super cheap + fast
???
I've found myself, SO many times, choosing a hack in FiloDB just to avoid object allocations on returns. This is a serious JVM deficiency for high-performance code.
* In Scala, we ended up moving above graph to offheap, which made debugging and readability much much harder
---
# Scala: Functional Transform Example
```scala
object CountEvens {
def apply(numbers: Seq[Int]): Int =
numbers.filter(n => (n % 2) == 0).length
}
```
--
* Allocate `scala.Function1` for inner closure
* For each number:
* Box number into `java.lang.Integer`
* Pass object into closure `apply` method:
* Unbox the integer
* Calculate `n % 2 == 0`
* Box result into `java.lang.Boolean`
* Keep only numbers where above condition true
* Count total elements
---
# Rust: Performance AND Abstractions
```rust
pub fn count_evens(nums: Vec) -> usize {
nums.iter().filter(|&x| x % 2 == 0).count()
}
```
Rust is able to fully optimize nice functional transforms with inlining and no boxing.
* For each number:
* Calculate `n % 2 == 0`
* Keep only numbers where above condition true
* Count total elements
---
# Use case: Rust in Data Processing
Safe + fast as C + abstractions + easy SIMD => WIN!
* [Timely Dataflow](https://github.com/TimelyDataflow/timely-dataflow) - distributed data-parallel compute engine
* [DataFusion](https://arrow.apache.org/blog/2019/02/04/datafusion-donation/) - Apache Arrow's query engine
* [Weld](https://github.com/weld-project/weld) - Stanford's hi-perf IR for analytics
* [MinSQL](https://github.com/minio/minsql/blob/master/README.md)
* [Sled](https://github.com/spacejam/sled) - lock-free database engine
* [Ripgrep](https://github.com/BurntSushi/ripgrep) - insanely fast grep utility
* [Are we learning yet?](http://www.arewelearningyet.com) - list of ML Rust crates
---
# Lessons Learned
* Getting past borrow checker and compiler errors is hard at first.
* Discord, Rust community everywhere is super helpful
* Rust doesn't have REPL :(
* JVM/Scala is probably more productive for many use cases. But where Rust is good, it's really good
* Cargo is fairly awesome build tool. Sorry, way ahead of SBT.
* Learning Rust has improved my programming everywhere
* Rust works because safety is *universal* and *pervasive*, and the community has made it a priority
* Partial safety is not real safety
* Rust forces safety to be IN YOUR FACE ALL THE TIME.
???
especially about what it means to be safe.
* Being welcoming and inclusive has to be a foundational value of a community, hard to add it after the fact
In your face - maybe that's the way it should be
---
# Rust vs Scala, or Rust + Scala?
.left-column[
### Rust Standalone
* Complete, standalone Rust app
* Good for trying out in microservice architecture
]
.right-column[
### Rust + Scala
* Use Scala for what it's good at: coordination, networking, distributed systems
* Embed some Rust for high-performance data processing / concurrency with no bugs
* Use Rust as a .so / .dylib within JVM
]
---
# Rust + Scala
* [JnrFFI](https://github.com/jnr/jnr-ffi) - call into Rust via C FFI.
* Fast, simple, no SWAG/headers to compile
* [j4rs](https://astonbitecode.github.io/blog/post/j4rs_0.6.0/) for calling Java from Rust
* [JavaCPP](https://github.com/bytedeco/javacpp)
* [GraalVM](http://graalvm.org) - Super promising direct integration of LLVM bitcode and JVM
Example:
* Use Scala/JVM for Zookeeper, distributed coordination, integration layers
* Use Rust for high-performance, memory-heavy portion
---
# Taking Safety back to Scala from Rust
```scala
final case class U8(addr: Long) extends AnyVal {
// Simple pointer math, by # of U8 elements (bytes)
final def add(offset: Int): U8 = U8(addr + offset)
//scalastyle:off method.name
final def +(offset: Int): U8 = U8(addr + offset)
final def getU8: Int = UnsafeUtils.getByte(addr) & 0x00ff
final def asU16: U16 = U16(addr)
final def asI32: I32 = I32(addr)
final def asMut: MutU8 = MutU8(addr)
}
final case class MutU8(addr: Long) extends AnyVal {
final def set(num: Int): Unit = UnsafeUtils.setByte(addr, num.toByte)
}
```
For offheap data management: explicit Ptr type >> using Longs or type aliases; separate Mutable type; explicit conversions to different widths
---
# Interesting Rust Crates/Frameworks
* https://www.arewewebyet.org
* http://crates.io
* Web Frameworks - Actix, Rocket, Gotham, Iron, Nickel
* Networking/HTTP - H2, Hyper, Tokio
* Fast text processing - nom, pest, regex
IDE Support - IntelliJ-Rust, VSCode, Sublime/Emacs/VIM/Atom + Racer
BTW....
* Swift on Linux is also a thing... https://swift.org/server/
---
# Thank You Very Much
More links:
* https://github.com/velvia/links/blob/master/rust.md
* https://brson.github.io/fireflowers/
---
class: center, middle
# Extra Slides
---
# Macros
* Standard language feature
* Used everywhere: `println!`, `vec!`, `dbg!`, etc. etc.
* AST-based, similar to Scala macros
New syntax (-ish):
```rust
let map = hashmap!{
"a" => 1,
"b" => 2,
};
```
Automatic method/trait derivation:
```rust
#[derive(Serialize, Deserialize)]
struct S {
#[serde(rename = "a")]
f: i32,
}
```
(Examples from https://words.steveklabnik.com/an-overview-of-macros-in-rust)
---
# Other neat type-level stuff
Typestate pattern in Rust: http://cliffle.com/blog/rust-typestate/
* Allow operations only for certain types/states
* Static nature of Rust traits lets you define methods only when a trait has parameter of certain type
```rust
/// Operations that are valid only in Start state.
impl HttpResponse {
fn status_line(self, code: u8, message: &str)
-> HttpResponse
{
// ...
}
}
/// Operations that are valid only in Headers state.
impl HttpResponse {
fn header(&mut self, key: &str, value: &str) {
// ...
}
fn body(self, contents: &str) {
// ...
}
}
```
---
# Scala: Functional Transform Example (II)
```
public final java.lang.Object apply(java.lang.Object);
descriptor: (Ljava/lang/Object;)Ljava/lang/Object;
flags: ACC_PUBLIC, ACC_FINAL, ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: invokestatic #31 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
5: invokevirtual #33 // Method apply:(I)Z
8: invokestatic #37 // Method scala/runtime/BoxesRunTime.boxToBoolean:(Z)Ljava/lang/Boolean;
11: areturn
```
* Scala closures are boxed. Thus, each Int needs to be boxed, passed to the apply() method of the closure as `java.lang.Object`, then unboxed
* The boolean result of the `filter` method needs to be boxed to `java.lang.Boolean`
* Due to the virtual interface method, and the many implementations of function1, the JVM probably cannot inline this method