Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create a generic struct with Option<T> without specifying T when instantiating with None

Tags:

rust

I have a

struct Foo<T>
where
    T: // ... some complex trait bound ...
{
    a: Bar,
    b: Option<T>,
}

When attempting to instantiate the struct with a b: None the compiler complains that it cannot infer the type and requires a type hint e.g. via the turbofish syntax. That is onerous on the caller because they will have to find a type that fulfills the trait bounds and import it despite not caring about that optional functionality.

I think what I am looking for would be a bottom type that automatically fulfills any trait bounds but cannot be instantiated so that None::<Bottom> could be used, but I have not found such a type in the documentation.

like image 684
the8472 Avatar asked Sep 05 '25 23:09

the8472


2 Answers

There's a feature in the works that allows specifying the never type as !. This is not present in stable Rust, so you need to use a nightly and a feature flag:

#![feature(never_type)]

fn thing<T>() -> Option<T> {
    None
}

fn main() {
    thing::<!>();
}

However, this doesn't work for your case yet (this is part of the reason that it's unstable):

#![feature(never_type)]

trait NothingImplementsMe {}

fn thing<T>() -> Option<T> 
    where T: NothingImplementsMe,
{
    None
}

fn main() {
    thing::<!>();
}
error[E0277]: the trait bound `!: NothingImplementsMe` is not satisfied
  --> src/main.rs:12:5
   |
12 |     thing::<!>();
   |     ^^^^^^^^^^ the trait `NothingImplementsMe` is not implemented for `!`
   |
   = note: required by `thing`

The very first unresolved question on the tracking issue is:

What traits should we implement for !?


Since this feature is both unstable and doesn't do what you want, you may want to consider creating your own bespoke "bottom" type:

trait AlmostNothingImplementsMe {
    fn foo();
}

struct Nope;
impl AlmostNothingImplementsMe for Nope {
    fn foo() { unimplemented!() }
}

fn thing<T>() -> Option<T> 
    where T: AlmostNothingImplementsMe,
{
    None
}

fn main() {
    thing::<Nope>();
}

To improve the UX of this, I'd suggest creating a builder of some type that starts you off with the faux-bottom type:

mod nested {
    pub trait AlmostNothingImplementsMe {
        fn foo();
    }

    pub struct Nope;
    impl AlmostNothingImplementsMe for Nope {
        fn foo() { unimplemented!() }
    }

    pub fn with_value<T>(t: T) -> Option<T> 
        where T: AlmostNothingImplementsMe,
    {
        Some(t)
    }

    pub fn without_value() -> Option<Nope> {
        None
    }
}

fn main() {
    nested::without_value();
}

You can see this similar pattern in crates like Hyper, although it boxes the concrete type so you don't see it from the outside.

like image 140
Shepmaster Avatar answered Sep 09 '25 05:09

Shepmaster


One option to avoid the need to the turbofish operator is to have a type alias:

trait MyTrait {}
impl MyTrait for () {}

struct Foo<T: MyTrait> {
    i: isize,
    o: Option<T>,
}

type Bar = Foo<()>;

fn main() {
    let foo_default = Bar { i: 1, o: None };
}

I used () as the default for simplicity, but ! (when available) or your own bottom type as in @Shepmaster's answer may be better.

A constructor function could also work if you don't mind Foo::new_default(i) or similar.

like image 44
Chris Emerson Avatar answered Sep 09 '25 06:09

Chris Emerson