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 andMatchArgalready needs it (Vec<MyEnum>→ STRSXP). Rather than a second, conflictingVec<T>blanket (E0119), both paths funnel throughIntoRVecElement:MatchArgtypes reach it via a bridge blanket inmatch_arg.rs, newtypes via a concrete impl emitted by#[derive(IntoR)]. A type that is both aMatchArgenum and anIntoRnewtype is a coherence error — don’t derive both on one type.IntoR for Option<T>(not granted). A bareOption<T>blanket collides with the pre-existingimpl<T: Copy + IntoR> IntoR for Option<&T>:&Tis#[fundamental], so a downstream crate could implIntoRNewtypefor&LocalTypeand coherence cannot prove the two disjoint. ReturnOption<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§
- IntoR
Newtype - Unwrap a forwarding newtype into its inner value (Rust → R side).
- IntoR
VecElement - How a
Vec<Self>becomes a single R vector SEXP. - Option
Inner 👻 Option<Self>: TryFromSexpwith an error that converts intoSexpError, withSelfas the bound’s self type.- TryFrom
Sexp Element - The element side of the
TryFromSexpimpls forVec<T>,Option<T>andVec<Option<T>>(R → Rust). - VecInner 👻
Vec<Self>: TryFromSexpwith an error that converts intoSexpError, withSelfas the bound’s self type.- VecOption
Inner 👻 Vec<Option<Self>>: TryFromSexpwith an error that converts intoSexpError, withSelfas the bound’s self type.