Rust: Difference between revisions

From Hegemon Wiki
Jump to navigation Jump to search
No edit summary
Line 4: Line 4:
Consider some context that has a random number generator. I was wanting something similar for a Genetic programming experiment:
Consider some context that has a random number generator. I was wanting something similar for a Genetic programming experiment:


==Without Traits==
pub struct Context {
pub struct Context {
rng: StdRng,
rng: StdRng,
Line 12: Line 13:
let seed: &[_] = &[1, 2, 3, 4];
let seed: &[_] = &[1, 2, 3, 4];
let mut rng = StdRng::from_seed(seed);
let mut rng = StdRng::from_seed(seed);
}
pub fn set_rng(&self, rng: StdRng) {
self.rng = rng;
}
}
}
}
Line 34: Line 38:
Using boxes allows for generics to be avoided.
Using boxes allows for generics to be avoided.


When adding a Box to the struct, it must be specified on the constructor, any mutator functions and the at the constructor call site. Users/consumers of the struct however are unaffected.
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 of the struct however are unaffected.


pub struct Context {
pub struct Context {

Revision as of 03:39, 10 December 2016

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

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)
}

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.

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 of the struct however are unaffected.

pub struct Context {
   rng: Box<Rng>,
}
impl Context {
   pub fn new(rng: Box<Rng>) -> Context {
       Context {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));
   use_context(&ctx)
}


Using &'s

Requires lifetime to be specified in generics.

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.