Pre-RFC: explicit proper tail calls

This would be emitted to LLVM musttail where possible, or to tail otherwise. LLVM does actually emit a tail call for tail in most cases; the remaining cases would become bugs that should eventually be fixed.

I do think that allowing a cast (either as or std::mem::transmute – the latter only in unsafe code, obviously, and only where compilable) of the return value would be valuable, but it can wait.

I think that become should be allowed almost anywhere that return currently is. become on an expression tries to tail-call the last operator in the expression. (While somewhat unusual, this can be useful due to operator overloading.). become on an if or match statement behaves as if propagated into each arm of the statement.

Your example would be written as:

fn fact_a(n: i32, a: i32) -> i32 {
    if n == 0 { a } else { become fact_a(n - 1, n * a) }
}

I am not sure whether this code should be accepted.

fn fact_a(n: i32, a: i32) -> i32 {
    become if n == 0 { a } else { fact_a(n - 1, n * a) }
}

The main problem with accepting it is figuring out where the locals of fact_a get dropped.

The reason the drop logic is needed is code like this:

fn id<a, b>(x: a, y: &mut b) -> a {
    x
}

fn foo<a>(x: Vec<a>, y: bool) -> Vec<a> {
    if y {
        x
    } else {
        let bar = ();
        let baz = Box::new(());
        become id(x, &mut bar)
    }
}

Here:

  • passing x to id is fine, since ownership of x is transferred to id.
  • Having baz in scope is okay, since it is not used after the tail call is made. But it cannot be dropped after id returns, since foo's stack frame got clobbered. So it must be dropped before jumping to id.
  • Passing &mut bar is not okay – it refers to an address in foo's stack frame, which was overwritten by id's.

If the become had been omitted, this would have been valid code, since bar would last until foo returned. But if we declared that all locals were dropped before the become, then x would have been destroyed before being passed to id.