ProgramaciónDesarrollador de bibliotecas / Rust Library Developer

¿Cómo se implementa el control de acceso a los campos de las estructuras en Rust y cómo se relaciona esto con la visibilidad de los módulos? ¿Cómo organizar correctamente las API públicas para minimizar los errores al usar estructuras fuera del módulo?

Supere entrevistas con el asistente de IA Hintsage

Respuesta.

Historia de la cuestión:

En lenguajes como C++ o Java, los modificadores de acceso (public, private, protected) proporcionan visibilidad a los miembros de una clase, pero de manera bastante flexible — y a menudo no evitan el uso erróneo de la API. En Rust, desde sus primeras versiones, se ha hecho que el sistema de control de acceso sea estricto y claramente modular, con el fin de limitar el "filtrado" de los detalles internos hacia el exterior.

Problema:

A menudo, es necesario ocultar parcialmente una estructura del código externo: por ejemplo, los campos son privados, y solo se exponen los métodos. Si no se limita el acceso, se pueden dañar involuntariamente los invariantes de la estructura (por ejemplo, exponiendo directamente un Vec interno). Publicar sin pensar una estructura hace que la API sea frágil.

Solución:

En Rust, todo es privado por defecto. La palabra clave pub se utiliza para exportar explícitamente: se puede declarar por separado la estructura como visible y los campos como ocultos. Los métodos se declaran públicamente o como privados de forma individual. Además, formas no estándar como pub(crate) o pub(super) permiten ajustar finamente el nivel de acceso.

Ejemplo de código:

mod domain { pub struct User { pub name: String, age: u32, // campo privado } impl User { pub fn new(name: String, age: u32) -> Self { Self { name, age } } pub fn age(&self) -> u32 { self.age } } } use domain::User; fn main() { let u = User::new("Eve".to_string(), 24); println!("{} {}", u.name, u.age()); // u.age — error de acceso! campo cerrado fuera del módulo }

Características clave:

  • Por defecto, todo es privado, incluidos los campos y funciones
  • pub expone solo los elementos seleccionados explícitamente
  • pub(crate), pub(super) proporcionan un ajuste de acceso flexible para proyectos grandes

Preguntas capciosas.

¿Se puede hacer una estructura pública, pero mantener todos sus campos privados? ¿Cómo se crean instancias de ella fuera del módulo?

Sí. Así es como se suele hacer: estructura pública, campos privados, creación solo a través de constructores públicos (por ejemplo, pub fn new...).

¿Se volverá visible un campo de la estructura al declarar pub struct Foo?

No, por defecto, cada campo sigue siendo privado — hay que declarar explícitamente pub para el campo. pub struct solo hace que el tipo sea visible.

¿Funciona pub para enum de la misma manera?

En enum, pub se aplica a todas las variantes, pero para los datos asociados en las variantes (por ejemplo, un campo dentro de Variant(value: T)) aún hay que especificar explícitamente pub si se desea que las entrañas sean accesibles.

Errores comunes y anti-patrones

  • Hacer pub todos los campos por simplicidad, rompiendo la encapsulación
  • Intentar acceder a campos privados directamente desde módulos externos (error de compilación)
  • Olvidar crear un método público para construir/modificar la estructura, si todos los campos están cerrados

Ejemplo de la vida real

Caso negativo

En la biblioteca, la estructura se declaró como pub struct Config, todos los campos también son pub — para que el usuario los "vea". Como resultado, cualquier código externo podía cambiar arbitrariamente el estado, rompiendo invariantes, causando panic de la nada.

Pros:

  • Máxima apertura y flexibilidad para el usuario

Contras:

  • Ruptura de la encapsulación
  • Dificultades con la migración y versionado del API
  • Aumento de errores debido al uso incorrecto de los campos

Caso positivo

La estructura Config es pública, todos los campos son privados. La configuración — solo a través de métodos de construcción, constructor por defecto o funciones setter/getter. Fuera del módulo no se pueden romper invariantes.

Pros:

  • API limpia, invariantes bajo control
  • Más fácil mantener la compatibilidad hacia atrás

Contras:

  • Para estructuras complejas — más código (métodos, constructores, pruebas)