3

It seems that the Rust compiler has different behaviour on where clauses.

mod sub {
    use std::mem;

    static mut FF : *const Foo = &NopFoo;

    pub trait Foo: Send + Sync {
        fn foo(&self);
    }

    pub struct NopFoo;

    impl Foo for NopFoo {
        fn foo(&self) { println!("Nop"); }
    }

    pub struct HelloFoo {
        pub num: i64,
    }

    impl Foo for HelloFoo {
        fn foo(&self) { println!("Hello, {}", self.num ); }
    }

    pub fn set_ff<M>(make_foo: M) -> bool
        where M: FnOnce() -> Box<Foo> // <== Here
    {
        unsafe {
            FF = mem::transmute(make_foo());
        }
        false
    }

    pub fn get_ff() -> Option<&'static Foo> {
        Some(unsafe { &*FF })
    }
}

fn main() {
    sub::get_ff().unwrap().foo();

    let f = sub::HelloFoo{num: 42};
    sub::set_ff(|| Box::new(f));

    sub::get_ff().unwrap().foo();
}

(Playground)

With a where clause it works fine, prints:

Nop
Hello, 42

If I remove the where clause from sub::set_ff() the Rust compiler reports errors E0277 and E0308:

mod sub {
    use std::mem;

    static mut FF : *const Foo = &NopFoo;

    pub trait Foo: Send + Sync {
        fn foo(&self);
    }

    pub struct NopFoo;

    impl Foo for NopFoo {
        fn foo(&self) { println!("Nop"); }
    }

    pub struct HelloFoo {
        pub num: i64,
    }

    impl Foo for HelloFoo {
        fn foo(&self) { println!("Hello, {}", self.num ); }
    }

    pub fn set_ff(make_foo: Box<Foo>) -> bool  // <== Here
    {
        unsafe {
            FF = mem::transmute(make_foo());
        }
        false
    }

    pub fn get_ff() -> Option<&'static Foo> {
        Some(unsafe { &*FF })
    }
}

fn main() {
    sub::get_ff().unwrap().foo();

    let f = sub::HelloFoo{num: 42};
    sub::set_ff(|| Box::new(f));

    sub::get_ff().unwrap().foo();
}

(Playground)

I thought it should work fine, but the compiler reports an error instead:

error: the trait bound `std::ops::FnOnce() -> Box<sub::Foo + 'static> + 'static: std::marker::Sized` is not satisfied [--explain E0277]
  --> <anon>:24:19
24 |>     pub fn set_ff(make_foo: FnOnce() -> Box<Foo>) -> bool
   |>                   ^^^^^^^^
note: `std::ops::FnOnce() -> Box<sub::Foo + 'static> + 'static` does not have a constant size known at compile-time
note: all local variables must have a statically known size

error: mismatched types [--explain E0308]
  --> <anon>:41:17
41 |>     sub::set_ff(|| Box::new(f));
   |>                 ^^^^^^^^^^^^^^ expected trait std::ops::FnOnce, found closure
note: expected type `std::ops::FnOnce() -> Box<sub::Foo + 'static> + 'static`
note:    found type `[closure@<anon>:41:17: 41:31 f:_]`

Why does the Rust complier require 'static and Sized in the second one and why does the first one work?

My OS and Rust versions:

➜  ~ uname -a
Linux laptop 4.2.0-35-generic #40~14.04.1-Ubuntu SMP Fri Mar 18 16:37:35 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
➜  ~ rustc --version
rustc 1.10.0-nightly (9c6904ca1 2016-05-18)
pretzelhammer
  • 13,874
  • 15
  • 47
  • 98
NeilShen
  • 558
  • 6
  • 16

1 Answers1

6

Short answer: the two pieces of code are not equivalent, the second one doesn't really even make sense. You probably want the first one.

Let's look at a simpler example:

trait Foo {
    fn foo(&self) {}
}

fn in_where<T>(x: T)
    where T: Foo
{
    x.foo()
}

fn in_declaration<T: Foo>(x: T) {
    x.foo()
}

fn in_type(x: Foo) {
    x.foo()
}

This captures the original case that uses the where, adds an identical case that places the trait bound in the generic declaration, and includes the failing case where the trait is directly used as the argument type.

The key point here is that the first two versions are not the same as the third. The working versions state that any type may be passed to the function by value, so long as it implements the Foo trait. The non-working version states that it accepts exactly one type, the trait's type itself.

As the compiler states:

the trait core::marker::Sized is not implemented for the type Foo + 'static

Foo + 'static does not have a constant size known at compile-time; all local variables must have a statically known size.

When one of the working versions is used, the compiler generates a version of the code for every concrete type used (a process called monomorphization). It knows how much space the type requires and can appropriately allocate space on the stack to accommodate it.

However, a trait creates an unsized type of the same name as the trait. The compiler doesn't know how much space to allocate, so it would be impossible to actually produce machine code for that function.

The trait type can be used, but only through a level of indirection (a trait object). Two common examples would be &Foo and Box<Foo>. Both of these indirectly access the underlying trait through a pointer. Since a pointer has a known size, code can be generated.

fn in_type_ref(x: &Foo) {
    x.foo()
}

fn in_type_box(x: Box<Foo>) {
    x.foo()
}

Further reading:


Why does the Rust complier require 'static

It doesn't. There's an implicit 'static bound added to the trait type because you haven't specified a lifetime. The complete type of the argument is Foo + 'static.

Community
  • 1
  • 1
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366