0

Is it possible to write this program without using the lifetime parameter in

pub fn anagrams_for<'a>(word: &'a str, 
        possible_anagrams: &'a [&'a str]) -> Vec<&'a str>

?


main.rs

pub fn is_anagram(one_word: &str, other_word: &str) -> bool {
    if one_word.len() != other_word.len() {
        return false;
    }
    for ch in one_word.chars() {
        if other_word.find(ch) == None {
            return false;
        }
    }
    true
}

pub fn anagrams_for<'a>(word: &'a str, possible_anagrams: &'a [&'a str]) -> Vec<&'a str> {
    let mut return_list = Vec::new();
    for &item_str in possible_anagrams.iter() {
        if is_anagram(word, item_str) {
            return_list.push(item_str);
        }
    }
    return_list
}

fn main() {
    let word = "inlets";
    let inputs = ["hello", "world", "listen", "pants"];
    println!("{:?}", anagrams_for(word, &inputs));
}
user366312
  • 16,949
  • 65
  • 235
  • 452

3 Answers3

1

Sort of. The program as written can use a 'static lifetime.

pub fn anagrams_for(word: &str, possible_anagrams: &[&'static str]) -> Vec<&'static str> {

However, this is overly restrictive. The correct way to write it is like this.

pub fn anagrams_for<'a>(word: &str, possible_anagrams: &[&'a str]) -> Vec<&'a str> {

This function can always be used in place of the first function.

It's important to understand that every reference in a function signature always has a lifetime. Much of the time, these are inferred, but there is always an equivalent function that assigns lifetimes to every reference. The previous function is the same as this.

pub fn anagrams_for<'a, 'b, 'c>(word: &'b str, possible_anagrams: &'c [&'a str]) -> Vec<&'a str> {
drewtato
  • 6,783
  • 1
  • 12
  • 17
1

It is not possible, from lifetime elision rules :

Each elided lifetime in input position becomes a distinct lifetime parameter. This is the current behavior for fn definitions.

(To understand that you might want to check lifetime positions)

According to rules, your function's definition will have 3 distinct lifetime parameter.

pub fn anagrams_for(word: &str, possible_anagrams: &[&str]) -> Vec<&str>

It is ok to have 3 distinct lifetime parameter but Rust is not able to choose which parameter should be used for the output position -> Vec<&str>.

One of the lifetime(or smaller) from input positions needs to be used on output position(of course it should be the lifetime that related with the returned value), unless you are not returning 'static lifetime, though you should explicitly declare that as Vec<&'static str> (this topic is related of ownership and borrowing, dangling pointers)

Note: By smaller I meant a lifetime that contained by the selected lifetime from input positions.


There is also one rule you might want to check

If there are multiple input lifetime positions, but one of them is &self or &mut self, the lifetime of self is assigned to all elided output lifetimes.

This means code like this will work for lifetime elision, but you can only return something from the &self, not from the others.

struct SomeStruct;
impl SomeStruct {
    pub fn anagrams_for(&self, word: &str, possible_anagrams: &[&str]) -> Vec<&str> { 
        //...
        //return value(s) should be related with `&self`
    }
}
Ömer Erden
  • 7,680
  • 5
  • 36
  • 45
1

Answer

You do need to include the lifetime parameter because lifetime elision can’t take place here. The error that the compiler generates includes the reason, which is that Rust can’t tell whether the returned value is borrowed from word or possible_anagrams.

A Hidden Problem

While the way that you have added the lifetimes does appease the compiler, there’s likely an unintended side-effect. Consider:

let inputs = ["hello", "world", "listen", "pants"];
let matches = {
    // Pretend this was getting user input.
    let word = String::from("inlets");
    anagrams_for(&word, &inputs)
};
println!("{matches:?}");

This won’t compile, because according to the function signature the return value of anagrams_for borrows from word, and word doesn’t live long enough. This shouldn’t matter though, because we know we aren’t borrowing from word. So let’s update the function signature to work:

pub fn anagrams_for<'a>(word: &str, possible_anagrams: &[&'a str]) -> Vec<&'a str> {
    let mut return_list = Vec::new();
    for &item_str in possible_anagrams.iter() {
        if is_anagram(word, item_str) {
            return_list.push(item_str);
        }
    }
    return_list
}

Here the lifetime of the return value is the same as the &str in possible_anagrams, which means it’s no longer bound to the lifetime of the test word.

Unrelated note:

The body of anagrams_for can also be written as:

possible_anagrams
    .iter()
    .map(|ana| *ana)
    .filter(|ana| is_anagram(word, ana))
    .collect()
PatientPenguin
  • 308
  • 1
  • 8