Skip to content

Suggestion for c_int in Listing 20-8 and 20-9 Using extern Functions to Call External Code to set a good example #4443

@nathany

Description

@nathany

URL to the section(s) of the book with this problem:

https://doc.rust-lang.org/stable/book/ch20-01-unsafe-rust.html#using-extern-functions-to-call-external-code

Description of the problem:

This listing is absolutely fine:

unsafe extern "C" {
    safe fn abs(input: i32) -> i32;
}

fn main() {
    println!("Absolute value of -3 according to C: {}", abs(-3));
}

But it may set a better precedent to use std::ffi::c_int for the parameter and return value of abs. While c_int and i32 should be equivalent on all modern platforms, that doesn't extend to c_long.

See the documentation for c_int and c_long, in particular this note for c_long:

This type will always be i32 or i64. Most notably, many Linux-based systems assume an i64, but Windows assumes i32.

C: making simple things complicated since 1972! 😄

I did a little experiment and found that:

If I erroneously declare labs to take a i64, then the Rust compiler will happily allow this runtime truncation to happen with no warning on Windows. Unsurprising, as Rust has no way to know that it should be 32-bit on Windows unless we tell it.

safe fn labs(input: i64) -> i64;

However, if we use c_long instead, then the Rust compiler can catch the mismatch at compile time:

use std::ffi::c_long;

unsafe extern "C" {
    safe fn labs(input: c_long) -> c_long;
}

Something like this:

|     println!("Absolute value of {num2} according to C: {}", labs(num2));
|                                                             ---- ^^^^ expected `i32`, found `i64`
|                                                             |
|                                                             arguments to this function are incorrect

Though note that this compiler error only happens on Windows, and I saw no such errors or clippy warnings on macOS. Writing cross-platform code is difficult!

So while safe fn abs(input: i32) -> i32 should be perfectly fine, it may be good if readers are aware of c_int and therefore adopt similar types at FFI boundaries.

Suggested fix:

use std::ffi::c_int;

unsafe extern "C" {
    safe fn abs(input: c_int) -> c_int;
}

fn main() {
    println!("Absolute value of -3 according to C: {}", abs(-3));
}

With minor updates to the surrounding prose.

Since this example is using 32-bit ints and since the truncation is well-defined implementation-specific C behaviour (and not strictly unsafe), I'm not sure if it this potential logic bug with c_long needs to be noted on the chapter on safety. What do you think?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions