Fix E0519: Private Type in Function Return Type

rust intermediate *

1. Symptoms

Error E0519 is a Rust compiler error that appears when you define a public function but the function’s return type references a type that is not accessible outside its defining module. The compiler prevents this because it would create an unusable API—callers would have no way to use the return value if they cannot see its type.

The error message in Rust looks like this:

error[E0519]: private type `InnerStruct` in return type of this function
  --> src/lib.rs:9:5
   |
9  |     pub fn get_inner() -> InnerStruct {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ private type in return type
   |
note: `InnerStruct` is private to this module
  --> src/lib.rs:1:1
   |
1  | struct InnerStruct {
   | ^^^^^^^^^^^^^^^^^^ private struct

The compiler explicitly tells you which type is private and where it is defined. In this example, InnerStruct is marked struct (implicitly private) at line 1, but get_inner() is marked pub fn at line 9. This combination is invalid because external code cannot construct or interact with the return value.

2. Root Cause

Rust’s module system enforces strict visibility boundaries. When you define a type without the pub keyword, that type is private to its containing module. Functions marked pub are part of your crate’s public API, which means external code will try to call them and use their return values.

If a public function returns a private type, external code faces an impossible situation:

  1. The caller cannot name the return type to declare a variable
  2. The caller cannot call methods on the return value that aren’t already in a public trait
  3. The caller cannot construct the type themselves

This violates Rust’s core guarantee that public API surfaces must be usable. The compiler rejects such code at compile time rather than producing confusing runtime errors.

Common scenarios that trigger E0519:

  • Returning a private struct: A function in a public API returns a struct without pub
  • Returning a private enum variant: Using an enum variant that isn’t marked pub in the return type
  • Returning a type alias to a private type: Creating a type Foo = PrivateType; and returning Foo
// This triggers E0519 - InnerData is private but appears in public return type
mod inner {
    pub struct OuterApi {
        // This function looks public but isn't actually usable
        pub fn process() -> ProcessedData {
            ProcessedData { value: 42 }
        }
    }

    struct ProcessedData {
        value: i32,
    }
}

The fundamental rule is: any type appearing in a public function’s signature must itself be public. This includes return types, parameter types, and associated types in trait implementations.

3. Step-by-Step Fix

The fix for E0519 involves making the type accessible that appears in your public function’s return type. There are several approaches depending on your design goals.

Fix Option 1: Make the Type Public

The simplest fix is to mark the returned type as pub:

Before:

mod processing {
    struct ProcessedData {
        value: i32,
    }

    pub fn create() -> ProcessedData {
        ProcessedData { value: 42 }
    }
}

After:

mod processing {
    pub struct ProcessedData {
        pub value: i32,  // Also make fields accessible if needed
    }

    pub fn create() -> ProcessedData {
        ProcessedData { value: 42 }
    }
}

fn main() {
    let data = processing::create();
    println!("Value: {}", data.value);
}

Fix Option 2: Use an Existing Public Type

If you don’t want to expose your internal structure, design a function that returns only public information:

Before:

mod config {
    struct Config {
        api_key: String,
        debug_mode: bool,
    }

    pub fn get_config() -> Config {
        Config {
            api_key: "secret".to_string(),
            debug_mode: false,
        }
    }
}

After:

mod config {
    pub struct ConfigView {
        pub debug_mode: bool,
    }

    pub fn get_config_view() -> ConfigView {
        ConfigView {
            debug_mode: false,
        }
    }
}

Fix Option 3: Return a Trait Object

For maximum encapsulation, return a trait object instead of a concrete type:

Before:

mod database {
    struct DatabaseConnection {
        // implementation details
    }

    pub fn connect() -> DatabaseConnection {
        DatabaseConnection {}
    }
}

After:

mod database {
    pub trait Connection {
        fn query(&self, sql: &str);
    }

    struct DatabaseConnection {
        // implementation details
    }

    impl Connection for DatabaseConnection {
        fn query(&self, sql: &str) {
            // implementation
        }
    }

    pub fn connect() -> impl Connection {
        DatabaseConnection {}
    }
}

fn main() {
    let conn: Box<dyn database::Connection> = database::connect();
    conn.query("SELECT 1");
}

Fix Option 4: Return a Reference to Public Data

When the returned data is actually owned by something public:

Before:

mod cache {
    struct CacheEntry {
        data: String,
    }

    pub fn get_cached() -> CacheEntry {
        CacheEntry { data: "cached".to_string() }
    }
}

After:

mod cache {
    pub struct CacheEntry {
        pub data: String,
    }

    impl CacheEntry {
        pub fn new(data: String) -> Self {
            CacheEntry { data }
        }
    }
}

fn main() {
    let entry = cache::CacheEntry::new("new data".to_string());
    println!("{}", entry.data);
}

4. Verification

After applying the fix, verify the error is resolved by compiling your code:

cargo build

A successful build produces no errors:

 Compiling myproject v0.1.0
  Finished dev [unoptimized + debuginfo] target(s) in 0.52s

You can also run tests to confirm the public API works correctly:

cargo test

Test that external code can actually use the return type:

// tests/integration_test.rs
#[test]
fn test_public_return_type_is_usable() {
    use mycrate::{create_processed_data, ProcessedData};
    
    let data = create_processed_data();
    // This line must compile - verifying the type is truly public
    let extracted_value: i32 = data.value;
    assert_eq!(extracted_value, 42);
}

Run the test to verify:

cargo test test_public_return_type_is_usable

If the test compiles and passes, your fix is complete. The returned type is now accessible to external code.

5. Common Pitfalls

Forgetting to Make Fields Public

Making the struct pub but keeping fields private is a common mistake:

mod inner {
    pub struct Data {
        // Field is still private!
        items: Vec<i32>,
    }

    pub fn create() -> Data {
        Data { items: vec![1, 2, 3] }
    }
}

fn main() {
    let data = inner::create();
    data.items.push(4); // ERROR: field `items` is private
}

The struct itself is public, but callers still cannot inspect or modify its contents. Use pub on individual fields when you want external access:

pub struct Data {
    pub items: Vec<i32>,  // Now accessible
}

Nested Privacy Gotchas

Privacy propagates through type definitions. Even if you make a struct public, its fields might reference private types:

pub struct PublicWrapper {
    pub inner: PrivateInner,  // E0519 again!
}

struct PrivateInner {
    // ...
}

You must make PrivateInner public as well, or redesign the structure to avoid exposing it.

Trait Objects and Hidden Types

When returning impl Trait, remember that callers may still need to name the type in some contexts:

pub fn create() -> impl TraitName {
    // ...
}

// Caller code that fails:
fn caller() -> impl TraitName {
    crate::create()  // This works
}

fn also_return_type() -> ReturnTypeOfCreate {
    // This fails - the concrete type is hidden
}

Use this approach only when callers don’t need to name the concrete type directly.

Re-exports and pub use

Using pub use to re-export types is a valid pattern, but it requires the original type to be public in its source module. You cannot export what isn’t first made accessible:

// In mycrate/src/lib.rs
mod internal {
    struct SecretType;  // Must be pub first!
}

pub use internal::SecretType;  // Will fail if SecretType is private

Documentation Blind Spots

Just because a type is pub doesn’t mean it’s meant for external use. Consider the semantic difference:

pub struct InternalType {
    // Document this clearly if it's only for advanced use cases
    pub data: [u8; 64],
}

Use documentation comments and convention (like pub(crate) for crate-internal) to communicate intent.

Error E0519 is part of a family of visibility-related compiler errors in Rust:

E0449: This error occurs when you mark an item pub inside a non-pub module, but the item is reachable through a public path. Unlike E0519 which focuses on return types, E0449 relates to re-exports and visibility propagation.

mod outer {
    pub mod inner {
        pub struct PublicStruct;
    }
}

use outer::inner::PublicStruct;  // Fine - path is public

E0603: Private enum variant used in public function signature. This is structurally similar to E0519 but specifically for enum variants:

enum Status {
    Active,          // Not marked pub
    Inactive,        // Not marked pub
}

pub fn get_status() -> Status {  // E0603
    Status::Active
}

E0625: Private type in trait return type. When implementing a trait publicly but using private associated types:

trait Processor {
    type Output;
}

struct PrivateType;

impl Processor for MyStruct {
    type Output = PrivateType;  // E0625 if trait is public
}

E0445: Private trait in public function return type with impl Trait. Similar pattern but for traits instead of structs:

trait PrivateTrait {  // Not pub
    fn process();
}

pub fn create() -> impl PrivateTrait {  // E0445
    // ...
}

These errors all share the same root cause: a public API surface that exposes something private. Understanding visibility rules in Rust’s module system prevents all of these errors. The key principle is consistency—everything that appears in a public signature must itself be public.

When you encounter E0519, the fix is always to either expose the type or redesign the API to avoid exposing it. Choose based on whether the type’s internals should be part of your public contract with users.