Program compiles even though a type cannot be inferred

5e34147
Opened by Adolfo Ochagavía at 2020-07-20 11:45:03

The code below compiles, even though the type of the elements of the Vec is unknown.

use std::mem;
use std::os::raw::c_void;

#[no_mangle]
pub extern "C" fn alloc(size: usize) -> *mut c_void {
    let mut buf = Vec::with_capacity(size);
    let ptr = buf.as_mut_ptr();
    mem::forget(buf);
    return ptr as *mut c_void;
}

fn main() { }

Somehow, Rust is picking a type and compiling anyway. It is unclear to me which type that may be and whether this behavior is specified somewhere. The type could be anything, from () to String or something else. You can test this by adding a type annotation to the declaration of buf. The code compiles regardless of the type you choose.

  1. Originally reported in https://github.com/badboy/hellorust/issues/4

    Adolfo Ochagavía at 2017-11-29 07:55:25

  2. I suppose when the compiler sees the expression x as A, it first tries to unify typeof(x) with A, and try coercion if the first unification fails.

    Therefore, my expectation is:

    • If the element type is not specified, the unification succeeds and the element type is c_void.
    • If the element type is specified, the unification fails and the as expression is assumed to do coercion.

    I have two collateral evidences: first, if I specify only part of the element type:

    use std::mem;
    use std::os::raw::c_void;
    
    #[no_mangle]
    pub extern "C" fn alloc(size: usize) -> *mut c_void {
        let mut buf = Vec::<(_, _)>::with_capacity(size);
        let ptr = buf.as_mut_ptr();
        mem::forget(buf);
        ptr as *mut c_void
    }
    
    fn main() { }
    

    then they claim type annotations needed. This can be explained as a result of unification failure at the as expression.

    Second, if I try to print the result of alloc and insert another as expression before ptr as *mut c_void:

    use std::mem;
    use std::os::raw::c_void;
    
    #[no_mangle]
    pub extern "C" fn alloc(size: usize) -> *mut c_void {
        let mut buf = Vec::<_>::with_capacity(size);
        let ptr = buf.as_mut_ptr();
        mem::forget(buf);
        // ptr as *mut (); // uncomment here
        ptr as *mut c_void
    }
    
    fn main() {
        println!("{:p}", alloc(10));
    }
    

    Then it changes the behavior. When I put ptr as *mut () before ptr as *mut c_void, it prints 0x1. That means the element type became (). Note that Rust changes allocation behavior on zero-sized types.

    So my suggestion is in your case, the element type is c_void.

    Masaki Hara at 2017-11-29 08:24:13

  3. Thanks for your reply, @qnighy. I just wanted to point out that specifying a different type does work (in your case, I guess using a tuple caused the error). For an example see this code:

    pub extern "C" fn alloc(size: usize) -> *mut c_void {
        let mut buf = Vec::<()>::with_capacity(size);
        let ptr = buf.as_mut_ptr();
        mem::forget(buf);
        return ptr as *mut c_void;
    }
    

    Adolfo Ochagavía at 2017-11-29 14:58:32

  4. What I meant was: the compiler does different things when the element type is specified and when it's not specified. This is because the type inference algorithm behaves ad-hoc in the place where coercion involves.

    in your case, I guess using a tuple caused the error

    No, the inference fails anytime when there is a hole in the element type. (_, _) fails. (u8, _) fails. (u32, u32) succeeds. Vec<_> fails. Box<_> fails. Box<str> succeeds. The only exception is when the whole element type is left blank. In this case, unification to c_void (which succeeds) fills in the hole.

    In short, there are only two cases the inference succeeds (if I understand correctly):

    • When the element type is fully specified. In this case, as *mut c_void is interpreted as a coercion, as the unification of *mut <element type> and *mut c_void fails.
    • When the whole element type is left blank. In this case, as *mut c_void is interpreted as an identity cast, as the unification of *mut <element type> and *mut c_void succeeds. Therefore the element type is inferred as c_void.

    I still don't know whether this ad-hoc inference behavior is considered stable or not.

    Masaki Hara at 2017-11-30 07:59:06

  5. Triage: this still compiles.

    Steve Klabnik at 2020-07-20 11:45:03