Why Rust Lifetime Elision cannot inference the proper lifetime annotations on functions?

Note that there's a meaningful terminology difference you're skipping over here. The reason we call it "lifetime elision" -- a different word from when we say "type inference" -- is that there's a very intentional choice that was made to not be smart here.

The philosophical reason here is that the signature is a firewall of sorts between the callers and the body. That wouldn't matter if everyone could write perfect code immediately, but I'm certainly not that good. So having that firewall is critical for having nice error messages, as it keeps a mistake inside a function from causing problems in the callers too.

Suppose, for example, that the compiler did infer exactly which lifetime goes with the return value, and you wrote this:

fn get_match_sub_vec(tar_vec: &Vec<&Vec<i32>>, given_vec: &Vec<i32>) -> Option<&Vec<i32>> {
    todo!("I'll get to this later")
}

The hypothetical lifetime inference would go "oh, well, there's no constraints on the output lifetime at all" and basically make it -> Option<&'static Vec<i32>>. But that's really not what you wanted, and will mean that any code you write calling the function is likely to pass borrowck because of that 'static, but would plausibly then stop compiling once you implement it properly.

Whereas with the elision rules, the body doesn't matter, and thus you have to say what you expect to happen, but in return that's what will happen. And all the callers can be type- and borrow-checked even if there's a mistake inside that particular function.

Once you know this general principal, you'll see it in more places.

For example, if Rust wanted to it could certainly make the following legal:

fn mul_add(a: i32, b: i32, c: i32) -> _ {
    a * b + c
}

After all, type inference has no trouble figuring out that the body returns i32, and thus it could know that the function should too.

But it doesn't, because that would let mistakes inside the function leak to callers. For example, imagine you'd typed this instead:

fn mul_add(a: i32, b: i32, c: i32) -> _ {
    a * b + c;
}

That's certainly an easy typographic mistake to make. And if the return type were inferred, it'd be completely valid (albeit silly) code for a function that's -> (). But because you need to specify -> i32 in the signature, it provides the firewall: the callers know that it's i32 despite the mistake in the body, and the body gets a "you probably want to remove this semicolon" suggestion on the type error.