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

Avatar for Zac Stewart

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`