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
toid
is fine, since ownership ofx
is transferred toid
. - Having
baz
in scope is okay, since it is not used after the tail call is made. But it cannot be dropped afterid
returns, sincefoo
's stack frame got clobbered. So it must be dropped before jumping toid
. - Passing
&mut bar
is not okay – it refers to an address infoo
's stack frame, which was overwritten byid
'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
.