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

Transport wrapper and capability examples

Status: Executable design example; not a stdlib API. These experiments use existing Plenty, supporting the assessment. They do not add stdlib types or change the language.

Baseline: three authors, three modules

Save the following files beside each other and run plentyc main.plenty. The library author owns library.plenty; the backend author owns backend.plenty; the application author owns main.plenty. Memory stands in for an external backend with success and failure outcomes. A TCP backend would need the partial-progress/error contract described in the assessment.

library.plenty:

pub enum TransportError:
    Closed
    TooLarge
    Backend

pub protocol Backend:
    def send_impl(self: &mut Backend, message: &str) -> Result[(), TransportError]:
        pass

pub class Transport[B]:
    _backend: B
    _limit: i64
    _open: bool
    _sent: i64

    pub def send(self: &mut Transport[B], message: &str) -> Result[(), TransportError]:
        if not self._open:
            return Err(TransportError.Closed)
        if message.byte_len() > self._limit:
            return Err(TransportError.TooLarge)
        match deliver(&mut self._backend, message):
            case Ok(_):
                pass
            case Err(cause):
                self._open = False
                return Err(cause)
        self._sent += 1
        Ok(())

    pub def sent(self) -> i64:
        self._sent

    pub def close(self: &mut Transport[B]) -> ():
        self._open = False

def deliver[B: Backend](backend: &mut B, message: &str) -> Result[(), TransportError]:
    backend.send_impl(message)

pub def transport[B: Backend](backend: B, limit: i64) -> Transport[B]:
    Transport(backend, limit, True, 0)

backend.plenty:

from library import TransportError

pub class Memory:
    pub def send_impl(self: &mut Memory, message: &str) -> Result[(), TransportError]:
        if message == "fail":
            return Err(TransportError.Backend)
        Ok(())

main.plenty:

from library import transport
from backend import Memory

def main() -> Result[(), Failure]:
    mut t = transport(Memory(), 4)
    print(str.repr(t.send("ok"))?)?
    print(str.repr(t.send("large"))?)?
    print(str.repr(t.send("fail"))?)?
    print(t.sent())?
    t.close()
    print(str.repr(t.send("ok"))?)?
    Ok(())

The calls produce success, TooLarge, Backend, count 1, and Closed. The application deliberately uses str.repr to show Result wrappers. The rejected oversized message never reaches the hook; a backend error poisons the transport and never increments the count. close is also an explicit operation.

transport[B: Backend] validates every requirement when constructed. The private deliver[B: Backend] helper supplies the bound for delegation from an otherwise unconstrained generic class method. This is existing Plenty; class Transport[B: Backend] is not available today.

An application cannot call t.send_impl (no such method) or t._backend (private field). It can construct a separate Memory and call its public send_impl directly. That separate value is not the owner moved into t, but separate handles could still alias the same external resource. Private wrapper fields do not establish exclusive access to the underlying socket or database.

Capability variant: authorize calls as well as implementations

Keep all three files above. In library.plenty, add this type before the protocol:

pub class Permit:
    _issued: bool

    def __del__(self: &mut Permit) -> ():
        pass

The generated field constructor is private because _issued is private. The destructor is deliberately empty: custom destruction disables structural copy. It is an awkward copy barrier, not cleanup logic or a proposed language idiom. The flag’s value is irrelevant; nominal type identity and private construction carry the authority.

Replace the protocol and private helper on the same page with:

pub protocol Backend:
    def send_impl(self: &mut Backend, message: &str, permit: &Permit) -> Result[(), TransportError]:
        pass

def deliver[B: Backend](backend: &mut B, message: &str) -> Result[(), TransportError]:
    permit = Permit(True)
    backend.send_impl(message, &permit)

In backend.plenty, import Permit alongside TransportError and add permit: &Permit after message: &str in Memory.send_impl. Its body can stay unchanged. The application file stays unchanged, and produces the same outcomes. All companion code for these replacements is the baseline on this page.

The external backend author can name the public type without constructing it. Ordinary application calls without a permit fail arity checking; both Permit(True) and Permit.new(True) fail private-constructor checking. Attempting saved = copy(permit).unwrap() in the backend fails with this resource cannot be copied. Removing the destructor makes that explicit copy legal, despite the private flag. This matters if the backend should be unable to retain an issued capability accidentally.

A hook can still invoke another hook with the borrowed permit during its call. The example does not prove exactly-once invocation, receiver/message identity, transaction sequencing, or correct external effects. Application code that is explicitly handed a permit also gains call authority. The library must choose which functions receive it.

Probe results

The compiler was freshly built from 5346f85 with cargo build --locked --offline --bin plentyc in the issue worktree, using a separate target directory. Both variants compiled and ran natively. These were focused manual probes, not additions to the tutorial’s executed-fence suite. Proposal fences intentionally use text.

Change to the exampleObserved result
Call t.send_impl("ok")Rejected: unknown method on Transport
Access t._backendRejected: private member
Make Memory.send_impl privateRejected at construction requiring the protocol; a matching name does not grant access
Make Memory privateRejected importing the class; a public method does not export its class
Call a separately constructed baseline backend directlyAccepted
Omit capability argument on a separate backendRejected: hook expects two arguments
Construct capability through either constructor spellingRejected: private Permit.__new__
Explicitly copy borrowed capabilityRejected with custom destruction; accepted without it

The source also confirms lexical access in src/frontend/protocols.rs (check calls check_member_at) and in protocol inference in src/frontend/generics.rs. Existing tests/test_modules.rs covers private classes/methods, transitive generic calls and definition-module access. The generic caller does not lend its access to the library’s helper.