Higher-ranked trait bounds

Migo·2024년 5월 15일
0

Fluent Rust

목록 보기
17/23
post-thumbnail

Lifetime in Rust can be tricky. In this article, when it gets trickier will be discussed.

Some design pain

Let's write the simplest generic trait as possbile

trait TDoSomething<T> {
    fn do_something(&self, value: T);
}

Ok, it takes T with no trait bound. Now, imagine there is some function that accepts any argument which has implemented TDoSomething:.

You may be thinking of something like this:

fn foo<T>(arg: impl TDoSomething<T>, arg2: T) {
    arg.do_something(arg2)
}

Well, this article is not for design pattern or the like but judging by the signature of the function, it is quite useless because if the arg was passed only to invoke associated function do_something with the argument, arg2 which was external to the function.

Simply put, it could've been a lot easier and straightforward to be processed outside the function. When it becomes useful is, when arg takes on the argument that's internal to the function:

fn foo<T>(arg: impl TDoSomething<T>) {
    let some_arg_created_inside_function = 0;
    arg.do_something(some_arg_created_inside_function) // Compile Error
}

Well, this time, compiler will complain as T IS NOT intger. How can we work around this? Obviously, you have to explicitly tell the type you expect.

fn foo(arg: impl TDoSomething<i32>) {
    let some_arg_created_inside_function = 0;
    arg.do_something(some_arg_created_inside_function)
}

But here comes real pain, what if you want to pass refenrece to it?

fn foo(arg: impl TDoSomething<&i32>) { // ERROR! expected named lifetime parameter
    let some_arg_created_inside_function = 0;
    arg.do_something(&some_arg_created_inside_function) // `some_arg_created_inside_function` does not live long enough

}

We got two error points here with compiler's explanations of why. Let's follow the suggestion.


fn foo<'a>(arg: impl TDoSomething<&'a i32>) { // Ok
    let some_arg_created_inside_function = 0;
    arg.do_something(&some_arg_created_inside_function) // `some_arg_created_inside_function` does not live long enough
}

While the first error about lifetime specification has gone, the latter one remains, Why?

Higher-rank trait bounds

Well, the named lifetime parameter like 'a is valid for as long as the function call stack is valid. But then we passed reference of some_arg_created_inside_function, meaning that it stays shorter than the expected lifetime.

But then how can we solve this problem? That's by further specifying lifetime with no function-level lifetime paramter.

fn foo(arg: impl for<'a> TDoSomething<&'a i32>) { 
    let some_arg_created_inside_function = 0;
    arg.do_something(&some_arg_created_inside_function); // Works fine!
}

Note that for<'a> doesn't mean that it narrow down the lifetime to only the inside the function. The formal definition of for<'a> is expressed as

"for all choices of 'a", and basically produces an infinite list of trait bounds that F must satisfy."

profile
Dude with existential crisis

0개의 댓글