Skip to content

Latest commit

 

History

History

2_6_sealing

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

Step 2.6: Sealing

Estimated time: 1 day

Sealing, in programming, usually means that some API (mostly public) cannot be inherited, extended or implemented outside its definition place. For example, a sealed class or interface in Kotlin cannot be inherited or implemented outside the library where it's defined. In Rust, this idiom may be applied to traits.

Traits

Sealed trait is a publicly accessible trait, which cannot be implemented outside its definition place (module or crate, depending on the visibility of this trait).

/// This trait is sealed and cannot be implemented for types outside this crate.
pub trait TheTrait: private::Sealed {
    // Zero or more methods that the user is allowed to call.
    fn ...();

    // Zero or more private methods, not allowed for user to call.
    #[doc(hidden)]
    fn ...();
}

// Implement for some types.
impl TheTrait for usize {
    /* ... */
}

mod private {
    pub trait Sealed {}

    // Implement for those same types, but no others.
    impl Sealed for usize {}
}

The empty private Sealed supertrait cannot be named by downstream crates, so we are guaranteed that implementations of Sealed (and therefore TheTrait) only exist in the current crate.

This is the most common way to seal a trait. The boilerplate could be completely cut off by using a sealed crate, providing a convenient macro to generate the one:

use sealed::sealed;

#[sealed]
pub trait TheTrait {}

#[sealed]
impl TheTrait for usize {}

However, there are alternative ways to seal a trait via its method signature, or even seal it partially.

The main purpose of sealing a trait is, of course, future-proofing of APIs.

We are free to add methods to TheTrait in a non-breaking release even though that would ordinarily be a breaking change for traits that are not sealed. Also we are free to change the signature of methods that are not publicly documented.

It's important to note that trait sealing fully relies on tricking over visibility rules (using a public supertrait or type, which name is not publicly exported), and so, has no impact on the type system semantics (a sealed public trait is just a regular public trait from the type system perspective). In theory, sealing a trait should affect its coherence, by relaxing its strictness for the use-cases which can never happen with a sealed trait. However, that would require a special support by compiler, which seems not gonna happen in the near future.

For better understanding traits sealing, its design and use-cases, read through the following articles:

Task

Seal the traits defined in this step's crate in the following way:

  • Make the MyIteratorExt trait fully sealed. Do it manually, using the sealed crate or a similar one is not allowed.
  • Make the MyError trait partially sealed. Only seal the method marked with #[doc(hidden)] attribute.
  • Sealing should work on both module level (disallowing to implement the sealed trait or the sealed method in the root module of the crate or any other module outside the one where the traits are defined, prove it by providing commented implementations in the root module of the crate, which doesn't compile due to the seal, if uncommented) and crate level (prove it by creating documentation tests which doesn't compile due to the seal).

Questions

After completing everything above, you should be able to answer (and understand why) the following questions:

  • What does sealing mean in programming in a broad sense?
  • What is trait sealing in Rust? When is it useful?
  • What limitations does trait sealing in Rust have? What could it be able to provide if supported by compiler?