A public struct can still keep fields private. Public methods become the supported way to build and read the value.

Program

Play the program to choose the retry count passed through a public constructor.

private_fields.rs
mod settings {
    pub struct Config {
        pub name: &'static str,
        retries: i32,
    }

    impl Config {
        pub fn new(name: &'static str, retries: i32) -> Self {
            Self { name, retries }
        }

        pub fn summary(&self) -> String {
            format!("{}:{}", self.name, self.retries)
        }
    }
}

fn main() {
    let retries = ;
    let cfg = settings::Config::new("api", retries);
    let label = cfg.summary();
    println!("{label}");
}
mod settings {
    pub struct Config {
        pub name: &'static str,
        retries: i32,
    }

    impl Config {
        pub fn new(name: &'static str, retries: i32) -> Self {
            Self { name, retries }
        }

        pub fn summary(&self) -> String {
            format!("{}:{}", self.name, self.retries)
        }
    }
}

fn main() {
    let retries = ;
    let cfg = settings::Config::new("api", retries);
    let label = cfg.summary();
    println!("{label}");
}
mod settings {
    pub struct Config {
        pub name: &'static str,
        retries: i32,
    }

    impl Config {
        pub fn new(name: &'static str, retries: i32) -> Self {
            Self { name, retries }
        }

        pub fn summary(&self) -> String {
            format!("{}:{}", self.name, self.retries)
        }
    }
}

fn main() {
    let retries = ;
    let cfg = settings::Config::new("api", retries);
    let label = cfg.summary();
    println!("{label}");
}
private field `retries` has no `pub`, so callers cannot set it directly.
constructor `Config::new` is the public creation path.
method `summary` exposes a stable view without exposing every field.