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

Closed

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?

Activity

  1. nathany commented on Jul 13, 2025

    @nathany
    Author

    I explored this topic a little more and wrote up a blog post going over it.
    https://nathany.com/labs/

    Hope it's helpful. Thanks.

  2. StefanSalewski commented on Jul 13, 2025

    @StefanSalewski

    Thanks for reporting. I agree that caring for compatible data types is important -- I think I took some care in my own book, but I have to agree that I don't know much about Rust's FFI. But extending that section might be an option -- you might have a look and tell me if there is something wrong: https://rust-for-c-programmers.com/ch25/25_4_interfacing_with_c_code_ffi.html

    I might read your blog post, and perhaps I will add it to the list in the appendix.

  3. StefanSalewski commented on Jul 14, 2025

    @StefanSalewski

    OK, I have added your blog post to the appendix (currently second link from the bottom). But I am still unsure how much room FFI should occupy in a beginner book -- I think most beginners would not use FFI, and they actually should better not use it. For tiny languages we often need C FFI, but Rust has such a rich collection of libraries, that FFI is very rarely needed. But I agree, a short mention of the risk of incompatible data types in beginner books is useful. I just read your blog, I think it is mostly OK.

  4. nathany commented on Jul 14, 2025

    @nathany
    Author

    Thanks. I want to point out that I made a mistake in my article that I need to correct. It was called out by the fine folks on Reddit: https://www.reddit.com/r/rust/comments/1lyjok1/hello_ffi_foot_guns_at_the_labs/

  5. nathany commented on Aug 5, 2025

    @nathany
    Author

    I wondered if the tech review would mention the same concern, but it looks like #4460 doesn't make any changes to these listings.

  6. changed the title [-]Suggestion for c_int in Listing 20-9 Using extern Functions to Call External Code[/-] [+]Suggestion for c_int in Listing 20-9 Using extern Functions to Call External Code to set a good example[/+] on Aug 5, 2025
  7. changed the title [-]Suggestion for c_int in Listing 20-9 Using extern Functions to Call External Code to set a good example[/-] [+]Suggestion for c_int in Listing 20-8 and 20-9 Using extern Functions to Call External Code to set a good example[/+] on Aug 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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