Using map on Result with Lifetime parameter


#1

Hello!

I’m building a little tool to parse some output from a UNIX command
What I wanted was to capture the output, parse the lines to a struct and then do some computation

My current code:

    let output = Command::new("ps")
        .arg("aux")
        .output()
        .ok()
        .expect("Failed to execute command");

    let result = 
        String::from_utf8(output.stdout)
        .map(split_lines)
        .map(parse_lines_to_struct);

With split_lines being:

fn split_lines<'a>(all_lines: &'a String) -> Vec<&'a str> {
    all_lines
        .lines()
        .collect()
}

When running this, I get that the function needed on map should not have a lifetime param

error[E0631]: type mismatch in function arguments
  --> proce.rs:17:10
   |
17 |         .map(split_lines)
   |          ^^^ expected signature of `fn(std::string::String) -> _`
...
21 | fn split_lines<'a>(all_lines: &'a String) -> Vec<&'a str> {
   | --------------------------------------------------------- found signature of `for<'a> fn(&'a std::string::String) -> _`

I began to use the explicit lifetime param as my struct has a str field, which should not outlive the struct

struct Process<'a> {
    user: &'a str,
}

What part am I doing wrong? Should the struct not have a lifetime param or is there a way to use map or and_then with lifetime params?

Thank so much for this space :heart:


#2

The map() is receiving the String by value, which is to say it’s transferring ownership of the String to that function. That’s the error you’re seeing now: your function expects a reference but you get a value.

You can anchor the String to a local binding and then operate on its slice for the remainder of the code. So something like:

let s = String::from_utf8(...).unwrap(); // pick a proper error handling strategy if you want
let x = parse_lines_to_struct(split_lines(&s));

Alternatively, use str::from_utf8 with Output being the owner of the raw bytes - then your code should work but it should take &str as input to split_lines.


#3

You really don’t want to be doing the collecting if you can avoid it? What are you hoping to achieve by splitting the lines over just using iterators directly? I would probably do something like

use std::str;

fn get_result(std_out: &[u8]) -> Result<MyStruct, SomeErrorType> {
    let output = str::from_utf8(std_out)?;
    output.lines()
        .map(parse_line_into_struct)
}

or if you think the following is readable enough

use std::str;

fn get_result(std_out: &[u8]) -> Result<MyStruct, SomeErrorType> {
    str::from_utf8(std_out)?
        .lines()
        .map(parse_line_into_struct)
}

Note that for ? to work, you need to implement From<Utf8Error> for your error type.