Using a named lifetime in FromStr

So I have a struct that dissects a string and stores the relevant parts as fields. I thought why not implement std::str::FromStr for it like so:

struct X<'a> {
  a: &'a str,
  b: &'a str,
}

impl<'a> FromStr for X<'a> {
  type Err = Error;
  fn from_str(s: &'a str) -> Result<Self, Self::Err> {
  ...
  }
}

But I get:

error[E0308]: method not compatible with trait
  --> src\main.rs:33:5
   |
33 |     fn from_str(s: &'a str) -> Result<Self, Self::Err> {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ lifetime mismatch
   |
   = note: expected signature `fn(&_) -> Result<_, _>`
              found signature `fn(&'a _) -> Result<_, _>`
note: the anonymous lifetime as defined here...
  --> src\main.rs:33:5
   |
33 |     fn from_str(s: &'a str) -> Result<Self, Self::Err> {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...does not necessarily outlive the lifetime `'a` as defined here
  --> src\main.rs:30:6
   |
30 | impl<'a> FromStr for X<'a> {
   |      ^^

I mean I get the first part of the error since it is not precisely the same signature.
Is this something the compiler could accept if it was smart enough or is there something wrong?
The second note makes it sound like there would be problems but I don't get why since it works when I put the function in the impl X block.

This is just because FromStr isn't generic on the lifetime of the str. The following compiles just fine:

struct X<'a> {
  a: &'a str,
}

trait MyFromStr<'a>: Sized {
    type Err;
  fn from_str(s: &'a str) -> Result<Self, Self::Err> ;
}


impl<'a> MyFromStr<'a> for X<'a> {
  type Err = ();
  fn from_str(s: &'a str) -> Result<Self, Self::Err> {
    Ok(X{a: s})
  }
}

Thanks for the reply. I get that but the question is would it be sound to say the anonymous lifetime in the trait is now called 'a or does this fundamentally not work for some reason.

'a and '1 are completely unrelated to each other and transmuting '1 to 'a to satisfy your bounds would be wildly unsafe. You can't forget that the caller (the code that calls X::from_str) chooses 'a, not the callee (your FromStr::from_str). I don't even need the help of @steffahn to come up with an example where this would lead to UB :sweat_smile::

use std::str::FromStr;

#[derive(Debug)]
struct X<'a> {
    a: &'a str,
    b: &'a str,
}

#[derive(Debug)]
struct Error;

impl<'a> FromStr for X<'a> {
    type Err = Error;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let s: &'a str = unsafe { std::mem::transmute(s) };

        let [a, b] = s.split(' ').take(2).collect::<Vec<_>>().try_into().unwrap();

        Ok(Self { a, b })
    }
}

fn main() {
    let underlying_string = String::from("hello world");
    let s: &str = &underlying_string;

    let res = X::<'static>::from_str(s).unwrap();

    println!("{res:?}");
    drop(underlying_string);
    println!("{res:?}");
}

Playground.

1 Like

Yeah this is obviously unsafe but I was thinking if I write

fn from_str(s: &'a str) -> Result<Self, Self::Err> {

this would (or rather that it could in principle) relate the lifetimes of the string parameter with the lifetime in Self.
And therefore the compiler to reject your

let res = X::<'static>::from_str(s).unwrap();

as it does when there is no trait involved.

Yeah, but that does not meet the API requirements of the FromStr trait, as @yyogo demonstrated above. Of course your function would be valid, but the API of FromStr requires from_str to work with any lifetime, not just with 'a. From the docs:

FromStr does not have a lifetime parameter, and so you can only parse types that do not contain a lifetime parameter themselves. In other words, you can parse an i32 with FromStr, but not a &i32. You can parse a struct that contains an i32, but not one that contains an &i32.

No, you can't since that's not how the trait is defined. The trait doesn't specify any relation between the lifetime of the &str and the type being parsed, so generic code that uses the trait can assume the parsed type does not borrow from that &str. Consider for example this function:

fn foo<T: FromStr>() {
    let s = String::from("foo");
    let t = T::from_str(&s).unwrap_or_else(|_| panic!());
    drop(s);
    // You can still use `t`, for example you can `drop` it:
    drop(t);
}

This function compiles, but will lead to UB if used with your implementation because the string s is deallocated before the last use of t.

1 Like

Thanks everybody I understand now.