rustprivate

How to print a variable of a private type that does not implement Display or Debug?


Imagine we have a private library to which we can make no changes, which contains some type that is not exposed in the library module.

// lib.rs
mod inner {
    pub struct SecretData {
        value: u32,
    }

    impl SecretData {
        fn new(value: u32) -> Self {
            SecretData { value }
        }
    }

    pub fn create_secret() -> SecretData {
        SecretData::new(42)
    }
}

pub use inner::create_secret;

In our application, we want to create a secret using the function provided by the library and then print out the secret.

// main.rs
use my_crate::create_secret;


fn main() {
    let secret = create_secret();
    println!("The secret is: {:?}", secret);
}

The above code in the main function will lead to an error indicating the Debug trait has not been implemented for the struct SecretData.

Is there any way in which we can print out the value of secret?


Solution

  • It's not possible in general.

    To be able to print meaningful data from a struct, you need to:

    1. either have code that prints it for you or converts it to a printable representation, i.e. any displaying trait from std::fmt, or a custom method like Path::display
    2. or know the memory layout of SecretData so you can safely read & print it yourself. That excludes all repr(Rust) structs.

    For a struct that you do know the memory layout of, you can transmute to a struct that does implement a displaying trait:

    mod inner {
        #[repr(C)]
        pub struct SecretData {
            value: u32,
        }
    
        impl SecretData {
            fn new(value: u32) -> Self {
                SecretData { value }
            }
        }
    
        pub fn create_secret() -> SecretData {
            SecretData::new(42)
        }
    }
    
    use inner::create_secret;
    
    #[repr(C)]
    #[derive(Debug)]
    struct NotSoSecret {
        v: u32,
    }
    fn main() {
        let secret = create_secret();
        // # Safety:
        // Both `SecretData` and `NotSoSecret` are `#[repr(C)]` structs with a single `u32` field so they both have the same memory layout.
        println!("The secret is: {:?}", unsafe {
            std::mem::transmute::<_, NotSoSecret>(secret)
        });
    }
    

    Note: Both #[repr(C)] (or another way to ensure the same memory layout) are required because otherwise the memory layout is unspecified which makes transmute UB.