Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

C++ and Rust precedents

Status: investigation evidence. Supports the library-owned operations assessment, not a change to Plenty’s protocol semantics. Sources and probes checked 2026-10-17.

C++: override and call access are separate

The library author can write:

class Transport {
public:
    void send() { validate(); send_impl(); record_success(); }
    virtual ~Transport() = default;
private:
    virtual void send_impl() = 0;
    void validate();
    void record_success();
};

The backend author overrides send_impl privately in a derived class. An application author calls send through Transport&. The wrapper statically selects the library’s algorithm, which dynamically invokes the backend hook. Access does not participate in deciding whether one virtual function overrides another; call accessibility is checked separately using the static expression type. C++ working draft: virtual functions, access to virtual functions.

A derived class can declare another send that hides the base name, or expose another public operation. Calling through the derived type can therefore select another API; NVI does not prevent that. The guarantee concerns calls through the base abstraction. In the focused probe, counters showed that Transport&.send used the wrapper and the private override, while Memory.send selected the derived hiding method. Direct calls to the private hook through either type were rejected. The probe compiled and ran with GCC 16.2.1, C++20 and -Wall -Wextra -Werror.

The issue’s Standard C++ FAQ link returned HTTP 403 during this investigation; the accessible working draft and compiled probe supplied the evidence instead. Exceptions/partial external effects still need a library-specific contract; this minimal example omits them.

Rust: composition supplies the fixed operation

The Rust Reference on traits specifies replaceable defaults and rejects visibility annotations on individual trait items. A public trait with default send and required send_impl therefore does not express either hidden hook access or a fixed algorithm. This is a constraint on that spelling, not on Rust’s ability to express the intent.

A library author can instead write this public surface, with private fields:

#![allow(unused)]
fn main() {
pub struct Permit { _private: () }
pub trait Backend {
    fn send_impl(&mut self, message: &str, permit: &Permit) -> Result<(), ()>;
}
pub struct Transport<B> { backend: B, sent: usize }

impl<B: Backend> Transport<B> {
    pub fn send(&mut self, message: &str) -> Result<(), ()> {
        if message.len() > 4 { return Err(()); }
        self.backend.send_impl(message, &Permit { _private: () })?;
        self.sent += 1;
        Ok(())
    }
}
}

A public constructor implemented in the library moves B into the wrapper. An external backend author implements Backend and names Permit, without creating it. An application author obtains Transport<B> and calls its inherent send. This excerpt isolates the mechanism; the complete Plenty example also specifies connection state and poisoning after backend failure.

Without the token, a separate backend value’s trait hook is callable. With it, the hook still has public visibility, but callers need a value they cannot construct. Permit exposes no constructor, Default, Copy or Clone. The borrowed permit cannot be retained as an owned token by safe code; storing its reference requires a compatible lifetime that a callback-local permit does not provide. This does not restrict external trait implementations. Rust’s privacy rules make public trait items accessible but preserve private struct fields.

This adds a wrapper, inherent implementation/bound, delegation and one public token type/parameter. Separate crates need the extension trait and its argument types exposed as public contracts. A private implementation trait is simpler when all backends belong to the library, but removes external extensibility. Documentation hiding alone supplies no call restriction.

Sealing does not hide callable hooks

The Rust API Guidelines describe sealing to restrict downstream implementations. That is a different permission from calling a hook. In a separate-crate probe, the library put pub trait Hook in a private module, made public Api inherit it, and implemented both for Concrete. An application still compiled this generic call:

#![allow(unused)]
fn main() {
fn bypass<T: Api>(value: &T) -> i32 { value.hook() }
}

The application did not need to name the hidden supertrait. Bounds provide access to supertrait associated items, as the Rust Reference explains. Thus neither a private module path nor sealing is proof of hidden call access.

Rust probe outcomes

Separate library, backend and application crates compiled with rustc 1.99.0, edition 2021 and -D warnings; the application verified one successful send, size rejection, backend error and a count of one. No unsafe code was used.

Focused probeResult
External backend implements token-bearing traitAccepted
Application constructs private-field tokenE0451
Application calls hook without tokenE0061
Backend tries to clone &Permit into owned PermitE0308: reference cloned; token does not implement Clone
Visibility annotation on trait methodE0449
Implementor replaces a default sendAccepted; executed replacement returned the alternative value
Application’s generic bound calls private-module supertrait hookAccepted and ran

These small probes validate the language mechanisms, not transport reliability or database transaction semantics. They support using composition as the baseline and capability possession as a separate design choice.