Skip to main content

Module newtype

Module newtype 

Source
Expand description

Container conversions for newtypes (#[derive(TryFromSexp)] / #[derive(IntoR)]) and string-parsed types (try_from_sexp_via_str_parse!). See the module docs, #844 and #1766. Container conversions for newtypes and string-parsed types.

#[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, error text and TryFromSexp::CHARACTER_ONLY in every shape.

A type parsed from an R string gets the same three shapes from try_from_sexp_via_str_parse!, which implements TryFromSexpElement with ParsedRStr as the inner type (#1766).

§The containers forward; a check refuses

The containers never call the element’s own TryFromSexp::try_from_sexp. Option<T> reads Option<T::Inner>, Vec<T> reads Vec<T::Inner> and Vec<Option<T>> reads Vec<Option<T::Inner>>, each wrapping what it read with TryFromSexpElement::from_inner. So an element’s scalar try_from_sexp must be the inner type’s followed by from_inner, which is what the derive emits, and refusing values a type’s inner type accepts belongs in TryFromSexpElement::check_sexp, not in try_from_sexp. The check runs on the R value before it is read, in the scalar try_from_sexp, on a non-NULL input for Option<T> (NULL is “not given”), and for Vec<T> / Vec<Option<T>> where R keeps the element’s class. #[derive(TryFromSexp)] takes it as #[try_from_sexp(validate = path)] (#1815).

§Where a Vec’s check runs: the inner container decides

An atomic vector keeps one class for all its elements, on the vector; a list of R objects keeps a class on each element. So the Vec blankets don’t run the check themselves: they pass T::check_sexp to the inner container’s hidden TryFromSexp::__mx_try_from_sexp_with_check (#1837). Its default runs the check once on the whole input, which every atomic inner container keeps. Vec<List> and Vec<Option<List>> override it to run the check on each element (but NULL) and to report every failing element in one error, with its position. The blankets override it too, adding their own check after the one they are given, so a newtype of a newtype over List checks each element, outer check first, as its scalar does.

This keeps one blanket per container slot and one element trait: the choice follows the inner type, through an impl that only miniextendr-api can write (Vec<List>), not a second marker trait a downstream type could implement next to TryFromSexpElement. The Option<T> blanket runs the check itself: a scalar keeps its class on itself whatever its inner type.

§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 (#844).

TryFromSexpElement / IntoRNewtype / IntoRVecElement are plumbing: they are emitted by the derives and by try_from_sexp_via_str_parse!, not implemented by hand. Implementing them manually is supported but unusual. A hand-written TryFromSexpElement also needs a TryFromSexp impl on the type for Option<T> to convert (the derive and the macro emit both), and that impl must keep the forwarding rule above: call TryFromSexpElement::check_sexp, then convert the inner type and wrap it, and nothing else, since no container ever runs it.

§One element trait per direction

Each TryFromSexp container slot (Vec<T>, Option<T>, Vec<Option<T>>) holds exactly one blanket, keyed on TryFromSexpElement. A second blanket on another public trait (say, a parse trait) would collide with it (E0119): a downstream type could implement both, so coherence cannot prove them disjoint. So every non-built-in element goes through this one trait, and what differs is the inner type the containers read first: the newtype’s field type, or ParsedRStr<T> for a string-parsed type, whose container impls in from_r hold the NA policy and the batched element errors.

Every container’s error is SexpError: the inner container’s error converts into it, and so does a check’s refusal (SexpError::Condition, carrying its own classes). The inner containers that exist are all miniextendr-api impls (the orphan rule keeps the rest out), and each one’s error is a SexpError or converts into one.

The blankets bound the inner type through the hidden VecInner / OptionInner / VecOptionInner helpers rather than Vec<T::Inner>: TryFromSexp. For a type that is not an element, T::Inner cannot be normalized; with the projection as the bound’s self type, the solver gives up on it at once and reports T: TryFromSexpElement as unsatisfied (E0277). Spelled Vec<T::Inner>: TryFromSexp, the same bound sent the solver through every Vec<_> impl (Vec<Vec<_>> nests one level per step) until it overflowed (E0275, #1682).

§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. The concrete TryFromSexp for Vec<List> / Vec<Option<List>> impls sit beside them because List is not a TryFromSexpElement, and no other crate can make it one (both are miniextendr-api’s).

Traits§

IntoRNewtype
Unwrap a forwarding newtype into its inner value (Rust → R side).
IntoRVecElement
How a Vec<Self> becomes a single R vector SEXP.
OptionInner 👻
Option<Self>: TryFromSexp with an error that converts into SexpError, with Self as the bound’s self type.
TryFromSexpElement
The element side of the TryFromSexp impls for Vec<T>, Option<T> and Vec<Option<T>> (R → Rust).
VecInner 👻
Vec<Self>: TryFromSexp with an error that converts into SexpError, with Self as the bound’s self type.
VecOptionInner 👻
Vec<Option<Self>>: TryFromSexp with an error that converts into SexpError, with Self as the bound’s self type.