Idea: add a `try_cast<T, U>(value: T) -> Result<U, T>` function?

Lifetimes are also commonly underdetermined. As long as they’re not overconstrained (i.e. with contradictory requirements), the compiler simply doesn’t care.[1] If you have some references, e.g.

let mut n1 = 1;
let mut n2 = 2;
let mut n3 = 3;
let r1 = &mut n1;
let r2 = &mut n2;
let r3 = &mut n3;
do_something();

do the three have the same lifetime? Is one longer than another other? The compiler doesn’t care. The code could be extended to

let mut n1 = 1;
let mut n2 = 2;
let mut n3 = 3;
let r1 = &mut n1;
let r2 = &mut n2;
let r3 = &mut n3;
n1 += 1;
do_something();
*r3 += 1;

now, the lifetime of r1 must be shorter than the one of r3! Because r1’s lifetime ends before the n1 += 1, and r3’s lifetime must stay alive until the *r3 += 1. What about r2 though? Is it the same as r1 or r3 or neither? The compiler doesn’t care, there still are no conflicting constraints, everything is fine.


Now of course, this approach of “the compile doesn’t care” would go entirely out of the window if you were to be able to ask the question “are the two lifetimes of these two types equal?”

In case the problems aren’t that obvious already by now, let me continue…

Imagine I’ll use your try_cast to implement some function are_the_types_the_same<U, T>(x: &mut U, y: &mut T) -> bool.

What is the output of

let mut n1 = 1;
let mut n2 = 2;
let mut n3 = 3;
let mut r1 = &mut n1;
let mut r2 = &mut n2;
let mut r3 = &mut n3;
dbg!(
    are_the_types_the_same(&mut r1, &mut r2),
    are_the_types_the_same(&mut r2, &mut r3),
);
n1 += 1;
do_something();
*r3 += 1;

it cannot be true twice… it could be false twice. If it’s only true for one of the two, which one and why? Whatever you think it should be, let’s take it further by adding calls to a function fn assert_same_type<T>(x: &mut T, y: &mut T) {}, making whole story even more complex:

let mut n1 = 1;
let mut n2 = 2;
let mut n3 = 3;
let mut r1 = &mut n1;
let mut r2 = &mut n2;
let mut r3 = &mut n3;
assert_same_type(&mut r1, &mut r2);
dbg!(
    are_the_types_the_same(&mut r1, &mut r2),
    are_the_types_the_same(&mut r2, &mut r3),
);
n1 += 1;
do_something();
*r3 += 1;

now (assuming the code still compiles) we expect the output (true, false); whereas with

let mut n1 = 1;
let mut n2 = 2;
let mut n3 = 3;
let mut r1 = &mut n1;
let mut r2 = &mut n2;
let mut r3 = &mut n3;
assert_same_type(&mut r2, &mut r3);
dbg!(
    are_the_types_the_same(&mut r1, &mut r2),
    are_the_types_the_same(&mut r2, &mut r3),
);
n1 += 1;
do_something();
*r3 += 1;

we expect the output (false, true). Right? Otherwise, the successful call to assert_same_type would contradict the are_the_types_the_same output. Of course combining both assert_same_type should result in compilation failure. (See also the real code example below.)

I would consider this some pretty freaky spooky action at a distance though. I do definitely not want a call to assert_same_type (which basically is more of a “if these types aren’t the same, please fail to compile”) function to have any influence on program behavior.

Here you can reproduce the compilation result of these examples without the hypothetical are_the_types_the_same function:

fn do_something() {}
fn assert_same_type<T>(x: &mut T, y: &mut T) {}

// works
fn test1() {
    let mut n1 = 1;
    let mut n2 = 2;
    let mut n3 = 3;
    let mut r1 = &mut n1;
    let mut r2 = &mut n2;
    let mut r3 = &mut n3;
    assert_same_type(&mut r1, &mut r2);
    // dbg!(
    //     are_the_types_the_same(&mut r1, &mut r2),
    //     are_the_types_the_same(&mut r2, &mut r3),
    // );
    n1 += 1;
    do_something();
    *r3 += 1;
}

// works
fn test2() {
    let mut n1 = 1;
    let mut n2 = 2;
    let mut n3 = 3;
    let mut r1 = &mut n1;
    let mut r2 = &mut n2;
    let mut r3 = &mut n3;
    assert_same_type(&mut r2, &mut r3);
    // dbg!(
    //     are_the_types_the_same(&mut r1, &mut r2),
    //     are_the_types_the_same(&mut r2, &mut r3),
    // );
    n1 += 1;
    do_something();
    *r3 += 1;
}

// fails
fn test3() {
    let mut n1 = 1;
    let mut n2 = 2;
    let mut n3 = 3;
    let mut r1 = &mut n1;
    let mut r2 = &mut n2;
    let mut r3 = &mut n3;
    assert_same_type(&mut r1, &mut r2);
    assert_same_type(&mut r2, &mut r3);
    // dbg!(
    //     are_the_types_the_same(&mut r1, &mut r2),
    //     are_the_types_the_same(&mut r2, &mut r3),
    // );
    n1 += 1;
    do_something();
    *r3 += 1;
}

(run in the playground)


  1. Unlike for types (except for their lifetime parameters), where an underdetermined, aka ambiguous, type will lead to compilation errors asking you to specify the type more precisely. ↩︎

4 Likes