Rust

From Hegemon Wiki
Revision as of 03:50, 10 December 2016 by H3g3m0n (talk | contribs) (→‎Using &'s)
Jump to navigation Jump to search

Trait Objects

Swapping between different ownership/reference types in Rust is a huge pain point. The generics can ripple throughout a program making maintenance/refactoring a big hassle.

Consider some context that has a random number generator. I was wanting something similar for a Genetic programming experiment:

Without Traits

This suffers from fixing the Rng to a specific implementation and prevents dependency inversion causing problems with things like testing and forcing any downstream users to be stuck with the choice.

extern crate rand;
use rand::StdRng;
pub struct Context {
   rng: StdRng,
}
impl Context {
   pub fn new() -> Context {
       let seed: &[_] = &[1, 2, 3, 4];
       let mut rng = StdRng::from_seed(seed);
   }
   pub fn set_rng(&self, rng: StdRng) {
       self.rng = rng;
   }
}
impl SomeTrait for Context {
    //...Blah
    fn some_function(self) {}
}
fn use_context(ctx: &Context) {
    //...Blah
}
fn main() {
   let mut ctx = Context::new();
   use_context(&ctx)
}

Using Box's

Using boxes allows for generics to be avoided.

When turning something into a Box on the struct, the struct's constructor must be updated, any mutator functions and the at the constructor call site. Users/consumers/owners of the struct however are unaffected.

This requires dynamic dispatch adding overhead.

extern crate rand;
use rand::{Rng, StdRng};
pub struct Context {
   rng: Box<Rng>, // <- This is changed
}
impl Context {
   pub fn new(rng: Box<Rng>) -> Context {  // <- Now this line must be changed
       Context {rng: rng}
   }
   pub fn set_rng(&self, rng: Box<Rng>) {  // <- ...this line must be changed
       self.rng = rng;
   }
}
impl SomeTrait for Context {
    //...Blah
}
fn use_context(ctx: &Context) {
    //...
}
fn main() {
   let seed: &[_] = &[1, 2, 3, 4];
   let mut rng = StdRng::from_seed(seed);
   let mut ctx = Context::new(Box::new(rng)); // <- ...this line must be changed
   use_context(&ctx)
}

Using &'s

Requires lifetime to be specified in generics.

extern crate rand; use rand::{Rng, StdRng}; pub struct Context<'a> { // <- This must be changed

  rng: &'a Rng, // <- So this can be changed

} impl Context<'a> { // The same generic signature must be added here

  pub fn new(rng: &Rng) -> Context<'a> {  // <- ...Here
      Context {rng: rng}
  }
  pub fn set_rng(&self, rng: &Rng) {
      self.rng = rng;
  }

} impl SomeTrait for Context<'a> { // <- ...Here

   //...Blah

} fn use_context(ctx: &Context<'a>) { // <- ...and here

   //...

} fn main() {

  let seed: &[_] = &[1, 2, 3, 4];
  let mut rng = StdRng::from_seed(seed);
  let mut ctx = Context::new(&rng);
  use_context(&ctx)

}

Using trait's instead of the struct

Requires traits to be defined. Still needs generics on the structs functions such as the constructor. However user/consumers functions are uneffected. Other structs that own this struct must now deal with the same trait object problem.

pub struct ContextRaw {
}

Using a global

Doesn't allow for multiple different contexts with their own separate Rng generators. Maybe some thread_local variant would work but that seems to be a pain and a hack. Globals suck.