Slide 1

Slide 1 text

Heterogeneous Collections in Rust

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

This talk

Slide 4

Slide 4 text

This talk • Homogeneous Collections

Slide 5

Slide 5 text

This talk • Homogeneous Collections • Generics

Slide 6

Slide 6 text

This talk • Homogeneous Collections • Generics • Trait Bounds

Slide 7

Slide 7 text

This talk • Homogeneous Collections • Generics • Trait Bounds • Trait Objects

Slide 8

Slide 8 text

This talk • Homogeneous Collections • Generics • Trait Bounds • Trait Objects • Static vs. Dynamic Dispatch

Slide 9

Slide 9 text

Homogeneous Collections

Slide 10

Slide 10 text

pub type Address = u16; pub struct Bucket { nodes: HashMap } pub struct Node { address: Address }

Slide 11

Slide 11 text

impl Bucket { pub fn get(&self, address: &Address) -> Option<&Node> { self.nodes.get(address) } pub fn insert(&mut self, node: Node) { self.nodes.insert(node.address, node); } }

Slide 12

Slide 12 text

// Put a node in the bucket... let mut bucket = Bucket::new(); let node = Node::new(0); bucket.insert(node); // And borrow it back to use it... let node_ptr = bucket.get(&0).unwrap(); node_ptr.send("hi!")

Slide 13

Slide 13 text

Homogenous Collections Simple to implement, but limited to the one type (Node) it was coded for.

Slide 14

Slide 14 text

Generics

Slide 15

Slide 15 text

pub struct Bucket { nodes: HashMap } pub struct UdpNode { address: Address } pub struct BluetoothNode { address: Address }

Slide 16

Slide 16 text

impl Bucket { pub fn get(&self, address: &Address) -> Option<&N> { self.nodes.get(address) } pub fn insert(&mut self, address: Address, node: N) { self.nodes.insert(address, node); } }

Slide 17

Slide 17 text

We can create a bucket that contains one type of node... let mut udp_bucket = Bucket::new(); let udp_node = UdpNode::new(0); udp_bucket.insert(udp_node, 0); Or the other let mut bluetooth_bucket = Bucket::new(); let bluetooth_node = BluetoothNode::new(1); bluetooth_bucket.insert(bluetooth_node, 1);

Slide 18

Slide 18 text

But not both let mut bucket = Bucket::new(); let udp_node = UdpNode::new(0); let bluetooth_node = BluetoothNode::new(1); bucket.insert(udp_node, 0); bucket.insert(bluetooth_node, 1); error[E0308]: mismatched types --> src/generics.rs:104:23 | 104 | bucket.insert(bluetooth_node, 1); | ^^^^^^^^^^^^^^ expected struct `generics::UdpNode`, | found struct `generics::BluetoothNode` | = note: expected type `generics::UdpNode` found type `generics::BluetoothNode`

Slide 19

Slide 19 text

Generics • More flexible than homogeneous collection, but may be too flexible: bucket.insert("I'm not a node".to_string(), 0) • Can't use any functions of the node structs since compiler can't know what type they are, other than N • A bucket can contain only one of any kind of struct

Slide 20

Slide 20 text

Generics Part 2: Trait Bounds

Slide 21

Slide 21 text

pub trait Node { fn address(&self) -> Address; } impl Node for UdpNode { fn address(&self) -> Address { self.address } } impl Node for BluetoothNode { fn address(&self) -> Address { self.address } }

Slide 22

Slide 22 text

Unbounded pub struct Bucket { nodes: HashMap } impl Bucket { pub fn get(&self, address: &Address) -> Option<&N> { self.nodes.get(address) } pub fn insert(&mut self, address: Address, node: N) { self.nodes.insert(address, node); } }

Slide 23

Slide 23 text

Bounded pub struct Bucket { nodes: HashMap } impl Bucket { pub fn get(&self, address: &Address) -> Option<&N> { self.nodes.get(address) } pub fn insert(&mut self, node: N) { self.nodes.insert(node.address(), node); } }

Slide 24

Slide 24 text

We can create a bucket that contains one type of node... let mut udp_bucket = Bucket::new(); let udp_node = UdpNode::new(0); udp_bucket.insert(udp_node); Or the other let mut bluetooth_bucket = Bucket::new(); let bluetooth_node = BluetoothNode::new(1); bluetooth_bucket.insert(bluetooth_node);

Slide 25

Slide 25 text

But not both let mut bucket = Bucket::new(); let udp_node = UdpNode::new(0); let bluetooth_node = BluetoothNode::new(1); bucket.insert(udp_node); bucket.insert(bluetooth_node); error[E0308]: mismatched types --> src/generics.rs:104:23 | 104 | bucket.insert(bluetooth_node); | ^^^^^^^^^^^^^^ expected struct `generics::UdpNode`, | found struct `generics::BluetoothNode` | = note: expected type `generics::UdpNode` found type `generics::BluetoothNode`

Slide 26

Slide 26 text

Trait-Bounded Generics • Controlled flexibility: buckets can only contain things that impl Node • Can call methods that trait Node declares: node.address() • A bucket can still only contain one type of thing that impements Node Heterogeneous potential, homogeneous usage

Slide 27

Slide 27 text

Trait Objects

Slide 28

Slide 28 text

Bounded generic pub struct Bucket { nodes: HashMap } impl Bucket { pub fn get(&self, address: &Address) -> Option<&N> { self.nodes.get(address) } pub fn insert(&mut self, node: N) { self.nodes.insert(node.address(), node); } }

Slide 29

Slide 29 text

Trait objects pub struct Bucket { nodes: HashMap> } impl Bucket { pub fn get(&self, address: &Address) -> Option<&Box> { self.nodes.get(address) } pub fn insert(&mut self, node: Box) { self.nodes.insert(node.address(), node); } }

Slide 30

Slide 30 text

impl UdpNode { pub fn send(&self, message: &str) { println!("Sending via UDP: {}", message); } } impl BluetoothNode { pub fn send(&self, message: &str) { println!("Sending via Bluetooth: {}", message); } }

Slide 31

Slide 31 text

pub trait Node { fn address(&self) -> Address; fn send(&self, message: &str); // <- move send declaration to here } impl Node for UdpNode { fn address(&self) -> Address { self.address } fn send(&self, message: &str) { println!("Sending via UDP: {}", message); } } // Same for BluetoothNode

Slide 32

Slide 32 text

We can put both kinds of nodes in the same bucket let mut bucket = Bucket::new(); let udp_node = UdpNode::new(0); let bluetooth_node = BluetoothNode::new(1); bucket.insert(Box::new(udp_node)); bucket.insert(Box::new(bluetooth_node)); We can borrow and use both kinds because they impl Node bucket.get(&0).unwrap().send("Sending with UDP node"); bucket.get(&1).unwrap().send("Sending with Bluetooth node");

Slide 33

Slide 33 text

• Buckets can only contain things that impl Node • Can call methods that trait Node declares • One bucket can store any mixture of things that imple Node • Anything borrowed from the bucket must be treated as a Node. Compiler can't know what kind of object was actually borrowed, just the trait.

Slide 34

Slide 34 text

Status vs. Dynamic Dispatch

Slide 35

Slide 35 text

use std::fmt::Display; trait Fancy: Display { fn fancy(&self) -> String; } impl Fancy for u8 { fn fancy(&self) -> String { format!("! {} !", self) } } impl Fancy for u16 { fn fancy(&self) -> String { format!("" {} "", self) } }

Slide 36

Slide 36 text

Static Dispatch fn print_fancy(thing: T) { println!("Static Print Fancy: {}", thing); } let number = 10u8; print_fancy(number); let number = 10u16; print_fancy(number);

Slide 37

Slide 37 text

Static Dispatch fn __print_fancy_for_u8(thing: u8) { println!("Static Print Fancy: {}", thing); } fn __print_fancy_for_u16(thing: u16) { println!("Static Print Fancy: {}", thing); } let number = 10u8; __print_fancy_for_u8(number); let number = 10u16; __print_fancy_for_u16(number);

Slide 38

Slide 38 text

Static Dispatch • Code path established at compile-time • Generally more efficient • Can lead to code "bloat" due to specific function for each type • Aggressive compiler "optimization" can actually lead to slower performance by bloating instruction cache • Rust stdlib prefers static-dispatch when possible

Slide 39

Slide 39 text

Dynamic Dispatch fn print_fancy(thing: &Fancy) { println!("Dynamic Print Fancy: {}", thing); } let trait_object = &10u8 as &Fancy; print_fancy(trait_object); let number = 10u16; print_fancy(&number);

Slide 40

Slide 40 text

Dynamic Dispatch is Provided by Trait Objects Trait Objects can be obtained by casting let trait_object = &10u8 as &Fancy; print_fancy(trait_object); ...or coercing let number = 10u16; print_fancy(&number);

Slide 41

Slide 41 text

Why Pointers? // 8 bits let num: [x|x|x|x|x|x|x|x] = 5; print_fancy(_|_|_|_|_|_|_|_) // 16 bits let num: [x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x] = 5; print_fancy(_|_|_|_|_|_|_|_) // can't fit :(

Slide 42

Slide 42 text

Why Pointers? // 8 bits let num: [x|x|x|x|x|x|x|x] = 5; let ptr: [size] = # print_fancy(size) // 16 bits let num = [x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x]; let ptr: [size] = # print_fancy(size) // ptrs are all the same size :)

Slide 43

Slide 43 text

Trait Objects can be other kinds of pointers, too let boxed = Box::new(10u8) as Box; let shared_reference = &10u8 as &Fancy; let mut_reference = &mut 10u8 as &mut Fancy; let raw_pointer = &10u8 as *const Fancy; let mut_raw_pointer = &mut 10u8 as *mut Fancy;

Slide 44

Slide 44 text

Object Safety let numbers = vec![1, 2, 3]; let trait_object = &numbers as &Clone; --> src/dispatch.rs:61:40 | 61 | let trait_object = &numbers as &Clone; | ^^^^^^ the trait `std::clone::Clone` cannot be made into an object | = note: the trait cannot require that `Self : Sized`

Slide 45

Slide 45 text