Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Heterogeneous Collections in Rust

Heterogeneous Collections in Rust

A short talk covering different kinds of polymorphism in Rust

Zac Stewart

May 18, 2017
Tweet

More Decks by Zac Stewart

Other Decks in Technology

Transcript

  1. This talk • Homogeneous Collections • Generics • Trait Bounds

    • Trait Objects • Static vs. Dynamic Dispatch
  2. pub type Address = u16; pub struct Bucket { nodes:

    HashMap<Address, Node> } pub struct Node { address: Address }
  3. 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); } }
  4. // 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!")
  5. pub struct Bucket<N> { nodes: HashMap<Address, N> } pub struct

    UdpNode { address: Address } pub struct BluetoothNode { address: Address }
  6. impl<N> Bucket<N> { 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); } }
  7. 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);
  8. 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`
  9. 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
  10. 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 } }
  11. Unbounded pub struct Bucket<N> { nodes: HashMap<Address, N> } impl<N>

    Bucket<N> { 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); } }
  12. Bounded pub struct Bucket<N: Node> { nodes: HashMap<Address, N> }

    impl<N: Node> Bucket<N> { 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); } }
  13. 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);
  14. 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`
  15. 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
  16. Bounded generic pub struct Bucket<N: Node> { nodes: HashMap<Address, N>

    } impl<N: Node> Bucket<N> { 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); } }
  17. Trait objects pub struct Bucket { nodes: HashMap<Address, Box<Node>> }

    impl Bucket { pub fn get(&self, address: &Address) -> Option<&Box<Node>> { self.nodes.get(address) } pub fn insert(&mut self, node: Box<Node>) { self.nodes.insert(node.address(), node); } }
  18. 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); } }
  19. 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
  20. 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");
  21. • 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.
  22. 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) } }
  23. Static Dispatch fn print_fancy<T: Fancy>(thing: T) { println!("Static Print Fancy:

    {}", thing); } let number = 10u8; print_fancy(number); let number = 10u16; print_fancy(number);
  24. 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);
  25. 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
  26. 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);
  27. 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);
  28. 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 :(
  29. Why Pointers? // 8 bits let num: [x|x|x|x|x|x|x|x] = 5;

    let ptr: [size] = &num; 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] = &num; print_fancy(size) // ptrs are all the same size :)
  30. Trait Objects can be other kinds of pointers, too let

    boxed = Box::new(10u8) as Box<Fancy>; 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;
  31. 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`