Wizard.Ritvik
Wizard.Ritvik

Reputation: 11612

Issue with lifetimes and type specifiers in struct

Im wrestling with an interesting problem I found with lifetimes and type specifiers in a struct definition. What I'm trying to do is restrict all fields with type S - which is defined as Into<Option<&str>>, so that I can pass either an Option or str as a value for the field - to a lifetime 'a which is defined as the struct's lifetime. I'm using rustc version 1.58.1 in case it helps.

Here's what I got working so far:

#[derive(Debug)]
struct A<S>
where
    S: Into<Option<&'static str>>,
{
    my_field: S,
}

fn main() {
    let obj = A {
        my_field: "hello world",
    };
    println!("{obj:?}");
}

I want to remove the 'static and restrict it to 'a. For reference I want to create multiple fields with a type S. I tried two variants but unable to get it working with either. Hopefully someone is able to shed light on what I'm doing wrong.

Variant #1

#[derive(Debug)]
struct A<'a, S>
where
    S: Into<Option<&'a str>>,
{
    my_field: S,
}

error:

error[E0392]: parameter `'a` is never used
  |
2 | struct A<'a, S>
  |          ^^ unused parameter
  |
  = help: consider removing `'a`, referring to it in a field, or using a marker such as `PhantomData

Variant #2

Trying where for.. as suggested here

#[derive(Debug)]
struct A<S>
where
    for<'a> S: Into<Option<&'a str>>,
{
    my_field: S,
}

error:

error: implementation of `From` is not general enough
   |
10 |     let obj = A {
   |               ^ implementation of `From` is not general enough
   |
   = note: `Option<&'0 str>` must implement `From<&str>`, for any lifetime `'0`...
   = note: ...but it actually implements `From<&'a str>`

Upvotes: 2

Views: 84

Answers (2)

Wizard.Ritvik
Wizard.Ritvik

Reputation: 11612

I went with suggestion by @Chayim Friedman and decided to move the bounds to the impl (in this case a function) where the struct is passed in to. So the use case here is that the struct would be used to build input data with optional fields, then pass it into a function.

This solves my immediate issue, but again is slight issue with the need to repeat the bounds on each function where I pass in input to. So for now I am adding all the logic under the one function directly, to avoid duplicating the bounds in other places.

#[derive(Debug)]
struct InputStruct<S1, S2> {
    my_field: S1,
    another_field: S2,
}

fn do_something_with_struct<'a, S1: Into<Option<&'a str>>, S2: Into<Option<&'a str>>>(
    input: InputStruct<S1, S2>,
) {
    // do something with input data
    let opt1 = input.my_field.into();
    let opt2 = input.another_field.into();
}

fn main() {
    let obj = InputStruct {
        my_field: "hello world",
        another_field: None,
    };
    println!("{obj:?}");

    do_something_with_struct(obj);
}

Upvotes: 0

Chayim Friedman
Chayim Friedman

Reputation: 70970

One way is to follow the compiler's suggestion and use PhantomData:

#[derive(Debug)]
struct A<'a, S>
where
    S: Into<Option<&'a str>>,
{
    my_field: S,
    _marker: PhantomData<&'a ()>,
}

You can also have a constructor, for convience, so you don't have to repeat the PhantomData.

Another way is to lift the bound to the impl, because the rules are less strict there:

#[derive(Debug)]
struct A<S> {
    my_field: S,
}

impl<'a, S> A<S>
where
    S: Into<Option<&'a str>>
{
    fn new(s: S) -> Self {
        Self { my_field: s }
    }
}

Upvotes: 2

Related Questions