rust-lang/rfcs

Ability to automatically derive traits on newtypes

rust-highfive opened this issue · 1 comments

Issue by Seldaek
Tuesday Aug 06, 2013 at 21:15 GMT

For earlier discussion, see rust-lang/rust#8353

This issue was labelled with: in the Rust repository


Disclaimer

Please bear with me, this contains long code samples but you can skip through most of them until the last one if you are only mildly interested, they're just there to illustrate my point.

Problem

While adding comments on rand::rng() I figured that it would be a lot better if the function would return a generic "interface" type for Rngs instead of returning the type of the particular struct it uses at the moment. However after discussing this with @kballard and @cmr on IRC, it seems that it is now pretty much impossible to achieve.

I'll go through the variants I researched, and then propose a solution (which may not be realistic, I have no clue about rustc internals, but I hope it is).

Failed attempt 1: Returning traits as a makeshift interface

This failed miserably because it's just not how the language works at all, and I got that now, but it's included for completeness.

use std::rand::{IsaacRng, XorShiftRng, RngUtil, Rng};

fn main() {
    let rng = rng();
    printfln!(rng.gen_bytes(10));
}

pub fn rng<T: Rng + RngUtil>() -> T {
    IsaacRng::new()
}

pub fn weak_rng<T: Rng + RngUtil>() -> T {
    XorShiftRng::new()
}
test.rs:6:4: 7:1 error: mismatched types: expected `T` but found `std::rand::IsaacRng` (expected type parameter but found struct std::rand::IsaacRng)
test.rs:6     IsaacRng::new()
test.rs:7 }
test.rs:10:4: 11:1 error: mismatched types: expected `T` but found `std::rand::XorShiftRng` (expected type parameter but found struct std::rand::XorShiftRng)
test.rs:10     XorShiftRng::new()
test.rs:11 }
test.rs:15:14: 15:31 error: the type of this value must be known in this context
test.rs:15     printfln!(rng.gen_bytes(10));
                         ^~~~~~~~~~~~~~~~~

Failed attempt 2: Using newtypes to "hide" the real implementation

This would be an ok workaround, it's not perfect but it seems to be workable given the constraints of the language and isn't too crazy, however it fails because it is missing the proper implementations:

use std::rand::{IsaacRng, XorShiftRng, RngUtil, Rng};

fn main() {
    let rng = rng();
    printfln!(rng.gen_bytes(10));
}

struct StrongRng(IsaacRng);

pub fn rng() -> StrongRng {
    StrongRng(IsaacRng::new())
}

struct WeakRng(XorShiftRng);

pub fn weak_rng() -> WeakRng {
    WeakRng(XorShiftRng::new())
}
test.rs:19:14: 19:31 error: failed to find an implementation of trait std::rand::Rng for StrongRng
test.rs:19     printfln!(rng.gen_bytes(10));
                         ^~~~~~~~~~~~~~~~~

Failed attempt 3: Implementing a decorator newtype

This probably is workable unless I don't get how to work around the compiler errors that are left, but I assume it's not a dead end. The problem though is that it is extremely verbose and seriously painful to write all this boilerplate just to have a straight decorator wrapping the underlying struct.

use std::rand::{IsaacRng, XorShiftRng, RngUtil, Rng, Weighted, Rand};

fn main() {
    let rng = rng();
    printfln!(rng.gen_bytes(10));
}

struct StrongRng(IsaacRng);
impl Rng for StrongRng {
    pub fn next(&mut self) -> u32 {
        return (**self).next();
    }
}
impl RngUtil for StrongRng {
    fn gen<T:Rand>(&mut self) -> T {
        return (**self).gen();
    }
    fn gen_int_range(&mut self, start: int, end: int) -> int {
        return (**self).gen_int_range(start, end);
    }
    fn gen_uint_range(&mut self, start: uint, end: uint) -> uint {
        return (**self).gen_uint_range(start, end);
    }
    fn gen_char_from(&mut self, chars: &str) -> char {
        return (**self).gen_char_from(chars);
    }
    fn gen_weighted_bool(&mut self, n: uint) -> bool {
        return (**self).gen_weighted_bool(n);
    }
    fn gen_str(&mut self, len: uint) -> ~str {
        return (**self).gen_str(len);
    }
    fn gen_bytes(&mut self, len: uint) -> ~[u8] {
        return (**self).gen_bytes(len);
    }
    fn choose<T:Clone>(&mut self, values: &[T]) -> T {
        return (**self).choose(values);
    }
    fn choose_option<T:Clone>(&mut self, values: &[T]) -> Option<T> {
        return (**self).choose_option(values);
    }
    fn choose_weighted<T:Clone>(&mut self, v : &[Weighted<T>]) -> T {
        return (**self).choose_weighted(v);
    }
    fn choose_weighted_option<T:Clone>(&mut self, v: &[Weighted<T>]) -> Option<T> {
        return (**self).choose_weighted_option(v);
    }
    fn weighted_vec<T:Clone>(&mut self, v: &[Weighted<T>]) -> ~[T] {
        return (**self).weighted_vec(v);
    }
    fn shuffle<T:Clone>(&mut self, values: &[T]) -> ~[T] {
        return (**self).shuffle(values);
    }
    fn shuffle_mut<T>(&mut self, values: &mut [T]) {
        (**self).shuffle_mut(values);
    }
}

pub fn rng() -> StrongRng {
    StrongRng(IsaacRng::new())
}

struct WeakRng(XorShiftRng);
impl Rng for WeakRng {
    pub fn next(&mut self) -> u32 {
        return (**self).next();
    }
}
impl RngUtil for WeakRng {
    fn gen<T:Rand>(&mut self) -> T {
        return (**self).gen();
    }
    fn gen_int_range(&mut self, start: int, end: int) -> int {
        return (**self).gen_int_range(start, end);
    }
    fn gen_uint_range(&mut self, start: uint, end: uint) -> uint {
        return (**self).gen_uint_range(start, end);
    }
    fn gen_char_from(&mut self, chars: &str) -> char {
        return (**self).gen_char_from(chars);
    }
    fn gen_weighted_bool(&mut self, n: uint) -> bool {
        return (**self).gen_weighted_bool(n);
    }
    fn gen_str(&mut self, len: uint) -> ~str {
        return (**self).gen_str(len);
    }
    fn gen_bytes(&mut self, len: uint) -> ~[u8] {
        return (**self).gen_bytes(len);
    }
    fn choose<T:Clone>(&mut self, values: &[T]) -> T {
        return (**self).choose(values);
    }
    fn choose_option<T:Clone>(&mut self, values: &[T]) -> Option<T> {
        return (**self).choose_option(values);
    }
    fn choose_weighted<T:Clone>(&mut self, v : &[Weighted<T>]) -> T {
        return (**self).choose_weighted(v);
    }
    fn choose_weighted_option<T:Clone>(&mut self, v: &[Weighted<T>]) -> Option<T> {
        return (**self).choose_weighted_option(v);
    }
    fn weighted_vec<T:Clone>(&mut self, v: &[Weighted<T>]) -> ~[T] {
        return (**self).weighted_vec(v);
    }
    fn shuffle<T:Clone>(&mut self, values: &[T]) -> ~[T] {
        return (**self).shuffle(values);
    }
    fn shuffle_mut<T>(&mut self, values: &mut [T]) {
        (**self).shuffle_mut(values);
    }
}
pub fn weak_rng() -> WeakRng {
    WeakRng(XorShiftRng::new())
}
test.rs:70:14: 70:31 error: multiple applicable methods in scope
test.rs:70     printfln!(rng.gen_bytes(10));
                         ^~~~~~~~~~~~~~~~~
note: in expansion of fmt!
<std-macros>:226:20: 226:37 note: expansion site
<std-macros>:224:4: 231:5 note: in expansion of printfln!
test.rs:70:4: 70:33 note: expansion site
test.rs:98:4: 100:5 note: candidate #1 is `__extensions__::gen_bytes`
test.rs:98     fn gen_bytes(&mut self, len: uint) -> ~[u8] {
test.rs:99         return (**self).gen_bytes(len);
test.rs:100     }
test.rs:70:14: 70:31 note: candidate #2 is `std::rand::__extensions__::gen_bytes`
test.rs:70     printfln!(rng.gen_bytes(10));
                         ^~~~~~~~~~~~~~~~~
note: in expansion of fmt!
<std-macros>:226:20: 226:37 note: expansion site
<std-macros>:224:4: 231:5 note: in expansion of printfln!
test.rs:70:4: 70:33 note: expansion site
test.rs:79:0: 122:1 error: multiple applicable methods in scope
test.rs:79 impl RngUtil for StrongRng {
test.rs:80     fn gen<T:Rand>(&mut self) -> T {
test.rs:81         return (**self).gen();
test.rs:82     }
test.rs:83     fn gen_int_range(&mut self, start: int, end: int) -> int {
test.rs:84         return (**self).gen_int_range(start, end);
           ...
test.rs:134:0: 177:1 error: multiple applicable methods in scope
test.rs:134 impl RngUtil for WeakRng {
test.rs:135     fn gen<T:Rand>(&mut self) -> T {
test.rs:136         return (**self).gen();
test.rs:137     }
test.rs:138     fn gen_int_range(&mut self, start: int, end: int) -> int {
test.rs:139         return (**self).gen_int_range(start, end);
            ...
error: aborting due to 3 previous errors

Proposal

The ideal way would be to allow the newtype to derive traits, so that making a decorator is not a PITA anymore. As you see right now it fails because the derive can't find the right impl, but I think it should just create it on the fly instead of complaining. That way we get explicit "interface" definition by defining which traits are implemented on the newtype, and we can return that and avoid a BC fiasco when we need to change the underlying RNG (see #8349 for more info on that).

I'd be happy to try and help implement this feature because I strongly believe this is needed, but to be honest I have no clue where to even start so pointers would be nice if the consensus is that it is a good thing and that it is at all feasible :)

use std::rand::{IsaacRng, XorShiftRng, RngUtil, Rng};

fn main() {
    let rng = rng();
    printfln!(rng.gen_bytes(10));
}

#[derive(Rng, RngUtil)]
struct StrongRng(IsaacRng);

pub fn rng() -> StrongRng {
    StrongRng(IsaacRng::new())
}

#[derive(Rng, RngUtil)]
struct WeakRng(XorShiftRng);

pub fn weak_rng() -> WeakRng {
    WeakRng(XorShiftRng::new())
}
test.rs:70:14: 70:31 error: failed to find an implementation of trait std::rand::Rng for StrongRng
test.rs:70     printfln!(rng.gen_bytes(10));
                         ^~~~~~~~~~~~~~~~~
note: in expansion of fmt!

Closing in favor of #261.