Lifetimes issues while implementing `FromStr` for my struct

Beginner here. I'm trying to impl FromStr for my Cedict struct and FromStr is giving me a lot of issues with lifetimes. I'd really like to understand why, but so far I've skated by on Clippy's recommendations to get things to compile. Here's the relevant code:

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct CedictEntry<'a> {
    pub traditional: &'a str,
    pub simplified: &'a str,
    pub pinyin: Option<Vec<Syllable<'a>>>,
    pub jyutping: Option<Vec<Syllable<'a>>>,
    pub definitions: Option<Vec<&'a str>>,
}

#[derive(Debug, Clone)]
pub struct Cedict<'a> {
    pub entries: Vec<CedictEntry<'a>>,
}

impl FromStr for Cedict {
    type Err = CedictEntryError;

    fn from_str(cedict_entries: &str) -> Result<Cedict, CedictEntryError> {
        let entries: Vec<CedictEntry> = cedict_entries
            .lines()
            .filter_map(|line| CedictEntry::new(line).ok())
            .collect();
        Ok(Cedict { entries })
    }
}

I get these errors:

   Compiling cccedict v0.1.3 (.../cccedict)
error[E0726]: implicit elided lifetime not allowed here
  --> src/cedict.rs:32:18
   |
32 | impl FromStr for Cedict {
   |                  ^^^^^^- help: indicate the anonymous lifetime: `<'_>`
   |
   = note: assuming a `'static` lifetime...

error: `impl` item signature doesn't match `trait` item signature
   --> src/cedict.rs:35:5
    |
35  |     fn from_str(cedict_entries: &str) -> Result<Cedict, CedictEntryError> {
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ found `fn(&str) -> Result<Cedict<'_>, CedictEntryError>`
    |
   ::: /Users/brian/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/src/rust/library/core/src/str/traits.rs:554:5
    |
554 |     fn from_str(s: &str) -> Result<Self, Self::Err>;
    |     ------------------------------------------------ expected `fn(&str) -> Result<Cedict<'static>, CedictEntryError>`
    |
    = note: expected `fn(&str) -> Result<Cedict<'static>, _>`
               found `fn(&str) -> Result<Cedict<'_>, _>`
    = help: the lifetime requirements from the `impl` do not correspond to the requirements in the `trait`
    = help: verify the lifetime relationships in the `trait` and `impl` between the `self` argument, the other inputs and its output

error: could not compile `cccedict` due to 2 previous errors

When I add explicit lifetimes:

impl<'a> FromStr for Cedict<'a> {
    type Err = CedictEntryError;

    fn from_str(cedict_entries: &'a str) -> Result<Cedict<'a>, CedictEntryError> {
        let entries: Vec<CedictEntry> = cedict_entries
            .lines()
            .filter_map(|line| CedictEntry::new(line).ok())
            .collect();
        Ok(Cedict { entries })
    }
}

I get these errors:

   Compiling cccedict v0.1.3 (.../cccedict)
error[E0308]: method not compatible with trait
  --> src/cedict.rs:35:5
   |
35 |     fn from_str(cedict_entries: &'a str) -> Result<Cedict<'a>, CedictEntryError> {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ lifetime mismatch
   |
   = note: expected fn pointer `fn(&str) -> Result<_, _>`
              found fn pointer `fn(&'a str) -> Result<_, _>`
note: the anonymous lifetime #1 defined on the method body at 35:5...
  --> src/cedict.rs:35:5
   |
35 |     fn from_str(cedict_entries: &'a str) -> Result<Cedict<'a>, CedictEntryError> {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...does not necessarily outlive the lifetime `'a` as defined on the impl at 32:6
  --> src/cedict.rs:32:6
   |
32 | impl<'a> FromStr for Cedict<'a> {
   |      ^^

For more information about this error, try `rustc --explain E0308`.
error: could not compile `cccedict` due to previous error

I wish I could ask a more specific question, but I'm just really at a loss :cry: Any help would be appreciated!

The way the FromStr trait is defined, you cannot produce a result which borrows from the input, because FromStr::from_str() always uses an input lifetime unrelated to Self. Your choices are:

  • Don't make Cedict borrow the input string, but own each piece (use String instead of &'a str). This also means that the Cedict is allowed to outlive the input, which might be desirable — right now, you're going to have to use the Cedict only within the stack frame that created it.
  • Don't use FromStr; just write your own function in impl Cedict {}. Then you're free to design the signature as you need it. (Or if you really want a trait, implement TryFrom<&'a str>.)

Another recent thread on the same problem:

2 Likes

Thank you, I'll likely try the first strategy! It's definitely premature, but is there a performance penalty to Cedict owning its own data? Also each CC-CEDICT dictionary file is on the order of a few MB of size.

You just need to declare the lifetime on the struct, not on the string argument.

impl<'a> FromStr for Cedict<'a> {
    type Err = CedictEntryError;

    fn from_str(cedict_entries: &str) -> Result<Self, Self::Err> {
        let entries: Vec<CedictEntry> = cedict_entries
            .lines()
            .filter_map(|line| CedictEntry::new(line).ok())
            .collect();
        Ok(Cedict { entries })
    }
}

If I do that, I get:

   Compiling cccedict v0.1.3 (.../cccedict)
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
  --> src/cedict.rs:37:14
   |
37 |             .lines()
   |              ^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime defined on the method body at 35:33...
  --> src/cedict.rs:35:33
   |
35 |     fn from_str(cedict_entries: &str) -> Result<Self, Self::Err> {
   |                                 ^^^^
note: ...so that reference does not outlive borrowed content
  --> src/cedict.rs:36:41
   |
36 |         let entries: Vec<CedictEntry> = cedict_entries
   |                                         ^^^^^^^^^^^^^^
note: but, the lifetime must be valid for the lifetime `'a` as defined on the impl at 32:6...
  --> src/cedict.rs:32:6
   |
32 | impl<'a> FromStr for Cedict<'a> {
   |      ^^
note: ...so that the expression is assignable
  --> src/cedict.rs:40:9
   |
40 |         Ok(Cedict { entries })
   |         ^^^^^^^^^^^^^^^^^^^^^^
   = note: expected `Result<Cedict<'a>, _>`
              found `Result<Cedict<'_>, _>`

For more information about this error, try `rustc --explain E0495`.
error: could not compile `cccedict` due to previous error

I guess since I don't have the actual implementations of the new method, the best I was able to come up with was a mock interface that compiled because it wasn't even remotely similar to what you're working with.

1 Like

Yes. You'll be making more (small) memory allocations and copying the data, so there will be extra memory needed (many small allocations to track vs. one large one) and time spent copying them. Whether this matters depends on how much you care about startup time and memory footprint.
You certainly should keep using the structure you have if you can, as it is efficient and elegant — it's just that you might find you can't.

(Note: Owned individual strings are not guaranteed to use more memory. if the CC-CEDICT format has lots of characters that are just structure syntax (e.g. indentation) and not part of the actual data, then storing the individual pieces will save memory over keeping the entire original file in memory.)

Another angle, more work but more flexible, is to use std::borrow::Cow<'a, str> as the string storage type — Cow allows a value to be either owned or borrowed, so you can construct a borrowed dictionary from the file and then, if you need to, convert it to a owned version (with type Cedict<'static>).

1 Like

Hi @kpreid I ended up switching everything to String instead of &str and things went a lot more smoothly. Thanks!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.