FromStr and lifetimes


#1

I have a struct that contains some &str, and I’m having trouble implementing FromStr for it, because the lifetime annotations don’t fit the trait. Here’s the reduced case:

https://play.rust-lang.org/?gist=a4d154d672e4edc98e8d7d1ffe45eb43&version=stable

In the example, in main s is of lifetime 'static, which is not true in my usecase, but I do not think it matters.

Here’s my problem: From main's point of view, everything should be ok: s lives longer than t, so t can contain a reference to s. But I need to convey that in the signature of the function, that the returned struct does not live longer than the reference passed in the function. The signature captures this meaning (putting it in an impl block for the struct makes it work).

But I get the following error message:

error[E0195]: lifetime parameters or bounds on method `from_str` do not match the trait declaration
  --> src/main.rs:11:5
   |
11 | /     fn from_str<'c, 'b:'c>(s: &'b str) -> result::Result<RunConfig<'c>, Self::Err> {
12 | |         Ok(RunConfig {name: s})
13 | |     }
   | |_____^ lifetimes do not match trait

Ok, it’s not exactly the right signature, but I need those lifetimes, otherwise it can’t conceivably work.

What am I to do? I do not want the structure to own the string, but only keep a reference. I could just not implement the trait and implement the function on the structure itself, but that feels wrong to me…

Thanks for any pointers :slight_smile:


#2

FromStr is meant for parsing an object out of a string slice, but not retain a reference to the slice (as you’ve discovered). Instead, just impl<'a> From<&'a str> for RunConfig<'a>. Do you specifically need FromStr? It looks like your conversion is infallible, so the Err associated type that’s needed by FromStr isn’t even applicable.


#3

I don’t really “need” it, but it means I can’t pass my struct to something that requires a T: FromStr. I have no idea what that would be, but I’m kindof discovering how to write rust code in a good way, and figured that implementing common traits would be a good thing.

My original use case has fallible conversion (that’s why I’m trying rust on that code in the first place, love the error handling!), that’s why it’s in there.

Thanks again for your helpfullness :+1:


#4

Implementing common traits is a good thing. But, From is by far more widely used than FromStr (perhaps for the very reason discussed in this thread, or at least partially for that).

I believe TryFrom is due to be stabilized, so that would be the avenue to pursue I think. Something like:

// feature gate needed for now (along with nightly channel compiler)
#![feature(try_from)]
use std::convert::TryFrom;

struct RunConfig<'a> {
    name: &'a str
}

impl<'a> TryFrom<&'a str> for RunConfig<'a> {
    type Error = Box<std::error::Error>;
    
    fn try_from(s: &'a str) -> Result<Self, Self::Error> {
        match s.len() {
            1 => Ok(RunConfig {name: s}),
            _ => Err("nope".into())
        }
    }
}

#5

Yeah that looks like a good thing. I’m on stable right now, but maybe later I’ll move to nightly. Upgrading is quite a hazzle for me, so I’ll skip that for now. Thanks again!


#6

Conceptual question: if you’re parsing data from a string, why do you need to retain the raw string? What does your real use case look like conceptually?


#7

Not sure I understand you, what’s the alternative? I could convert to a String alright, but I’m trying to learn, so I want to stay with references as far as possible (no need to modify things after initial parsing). Or do you mean I could save enum variants in my struct I parse into? I already do that in one case, but have not yet seen the need for others.

At least part of my use case will be to reconstruct the original string or parts of it or variants of parts of it later (to look for the existence of certain files based on those combinations of parts of that string).


#8

What I mean is that FromStr is meant to “deserialize” a value from a string representation. Let’s use the Point example straight from the rustdocs: https://doc.rust-lang.org/std/str/trait.FromStr.html - it reads out the x and y values, and then it’s done with the string. So think of from_str as a deserialization function (that doesn’t allow referencing the input) - it’s meant to produce a value from the string, but otherwise doesn’t care about the string. This is the “conceptual” bit I mentioned. It’s certainly possible that one may want to reference the string slice as part of the deserialized value, but that isn’t the intended use case for FromStr.

Perhaps you can share your real case and someone may suggest some alternatives.


#9

Sure thing :slight_smile: So I work on strings like AB123_XY_FTM27_99Q0001, which are actually coming from directory names, but I want to give them as commandline arguments. The parsing is actually depending on a namescheme, and I have not yet decided how to implement it, so I’ll just give the idea using this certain namescheme. Here:

  • AB denotes the manucfaturer
  • 123 is a project number
  • XY is a coarse classification of a car crash experiment
  • FTM27 is a finer grained classification, and FT denotes the the type of the barrier, M is the positioning (Middle) and 27 is the velocity in kph
  • 99 is a country code
  • Q denotes the engine and gear of the car
  • 0001 is a 4 or 5 digit number for versioning

After parsing, I’ll have to do certain tasks. Remember, I’m coming from a certain directory that contains (of course) certain files.

  • Based on AB123, XY, 99 and maybe an additional command line argument, find a certain directory. There, make a new directory based on 0001 and a command line argument, make a directory named like the original one, and put a certain subset of the files there
  • Write a .csv file with the info above, some of which needs custom transformation (i.e. the country code will need to be translated into a string for the country), others can stay as they are (the version number).
  • In the original directory, check if certain files exist. They either have a name that is not based on the original directory’s name (like channel), or a name that is the directories name with an extension (say AB123_XY_FTM27_99Q0001.pc).
  • In the original directory, open a certain file in a certain subdirectory (names as above), get line 7, get the number value of it (make sure it really was only a number there, maybe in scientific notation, maybe not), make sure it’s 0.
  • If there was more than 1 directory in the arguments, in all of them count the number of files in a certain subdirectory, and show an error if the number isn’t the same for all of them.

That’s roughly the stuff I want to do. I have a python script that does all this, but it doesn’t feel good, in particular wrt error handling (or rather, error handling feels painful in python to me, that’s why I don’t have enough of it).

Ok, that’s a lot, nobody needs to read that really, but since I was asked and got a lot of help, I of course wanted to put out all the details :slight_smile: Just a final note, performance is basically a non-issue (will be dwarfed by iO or external programs anyways), but me learning something interesting is of value :wink:


#10

So I guess the first design question is whether you want to parse that into structured data (types) upfront and then work off that or keep the raw string input and return slices from it to give back fragments of the encoded information. Your use case sounds a lot like uri handling in http libraries where a string consists of components that are extracted/parsed out by looking at separators and other bits of the uri. To that end, you can take some inspiration from how they’re designed and implemented.

If performance isn’t a concern, I wouldn’t think twice about extracting the stringly stuff from there into an owned String, even if you don’t intend to mutate later (you can also https://doc.rust-lang.org/std/string/struct.String.html#method.into_boxed_str the String to drop the capacity bit and enforce the readonly bit further).