Skip to main content

Module newtype

Module newtype 

Source
Expand description

Container conversions for forwarding newtypes (#[derive(TryFromSexp)] / #[derive(IntoR)]). See the module docs and issue #844. Container conversions for forwarding newtypes.

#[derive(TryFromSexp)] / #[derive(IntoR)] on a single-field newtype (struct UserId(Uuid)) emit the scalar forwarding impls and a small marker impl from this module. The container blankets here then light up Vec<UserId>, Option<UserId>, and Vec<Option<UserId>> automatically — the newtype inherits the inner type’s exact SEXPTYPE checks, NA policy, and error text in every shape.

§Why the markers live here and not in the derive

A downstream crate cannot write impl TryFromSexp for Vec<MyNewtype>: Vec / Option are not #[fundamental], so the local newtype is “covered” and the orphan rule (E0117) forbids it. The container impls must live in miniextendr-api, keyed on a marker trait the derive can legally implement downstream (foreign trait + local Self). The blankets below are those container impls. See analysis/rconvert-containers-coherence-2026-06-04.md (issue #844) for the full coherence analysis.

FromRNewtype / IntoRNewtype / IntoRVecElement are plumbing: they are emitted by the derives, not implemented by hand. Implementing them manually is supported but unusual.

§The asymmetries

Five of the six container shapes are granted. Two are not, for two different coherence reasons:

  • IntoR for Vec<T> (granted, but shared). This slot has exactly one blanket and MatchArg already needs it (Vec<MyEnum> → STRSXP). Rather than a second, conflicting Vec<T> blanket (E0119), both paths funnel through IntoRVecElement: MatchArg types reach it via a bridge blanket in match_arg.rs, newtypes via a concrete impl emitted by #[derive(IntoR)]. A type that is both a MatchArg enum and an IntoR newtype is a coherence error — don’t derive both on one type.
  • IntoR for Option<T> (not granted). A bare Option<T> blanket collides with the pre-existing impl<T: Copy + IntoR> IntoR for Option<&T>: &T is #[fundamental], so a downstream crate could impl IntoRNewtype for &LocalType and coherence cannot prove the two disjoint. Return Option<Inner> (opt.map(|x| x.0)) instead. See the note on the missing blanket below.

TryFromSexp for Vec<T> / Option<T> / Vec<Option<T>> and IntoR for Vec<Option<T>> are coherence-free: no other blanket occupies those slots.

Traits§

FromRNewtype
Construct a forwarding newtype from its inner value (R → Rust side).
IntoRNewtype
Unwrap a forwarding newtype into its inner value (Rust → R side).
IntoRVecElement
How a Vec<Self> becomes a single R vector SEXP.