A pattern for flexible factory references using Deref newtypes

A pattern for flexible factory references using Deref newtypes

This post is generated via Grok because my english isnt good.

Hi everyone!

I've been tinkering with a way to handle "factory" types (like ranges) that create child items needing a back-reference to the factory. The trick is supporting different ref kinds—&T, Rc<T>, etc.—without separate trait impls for each.

Rust's coherence rules block direct blanket impls, so I use a thin Deref newtype wrapper. It's a twist on the classic newtype pattern, but handy for this. Thoughts? Useful? Already in a crate somewhere?

Quick Example

Here's a generic countable range. Items hold a Deref ref to the range for bounds/indexing.

use std::ops::{Add, Deref, Range, RangeFrom, Sub};
use std::marker::PhantomData;
use std::fmt::Debug;

// Wrapper for any Deref<Target = Target>.
pub struct NTDeref<Ref, Target>(pub Ref, PhantomData<fn() -> Target>);

impl<Ref, Target> NTDeref<Ref, Target> {
    pub fn new(r: Ref) -> Self { Self(r, PhantomData) }
}

pub trait ToNTDeref<Target>
where Self: Deref<Target = Target> + Sized {
    fn as_nt_deref(self) -> NTDeref<Self, Target>;
}

impl<Ref, Target> ToNTDeref<Target> for Ref
where Ref: Deref<Target = Target> + Sized {
    fn as_nt_deref(self) -> NTDeref<Self, Target> {
        NTDeref::new(self)
    }
}

// Item trait: iterate and index relative to a range.
pub trait CountableRangeItem: Sized {
    fn next(self) -> Option<Self>;
    fn index(&self) -> usize;
    fn zero_assign(&mut self); // Reset to start.
}

// Factory trait: takes self by value (for ownership).
pub trait CountableRangeForRef {
    type Item: CountableRangeItem;
    fn item_first(self) -> Self::Item;
    fn item_from_index(self, index: usize) -> Option<Self::Item>;
    fn index_len(self) -> usize;
}

pub struct CountableRangeStd<T> { range: Range<T> }

pub struct CountableRangeStdItem<T, RangeRef>
where RangeRef: Deref<Target = CountableRangeStd<T>> {
    item: T,
    ref_range: RangeRef,
}

impl<T, RangeRef> Deref for CountableRangeStdItem<T, RangeRef>
where RangeRef: Deref<Target = CountableRangeStd<T>> {
    type Target = T;
    fn deref(&self) -> &Self::Target { &self.item }
}

impl<T, RangeRef> CountableRangeItem for CountableRangeStdItem<T, RangeRef>
where
    RangeRef: Deref<Target = CountableRangeStd<T>>,
    RangeFrom<T>: Iterator<Item = T>, // a super weird way to use `Step`
    T: Copy + Ord + Sub<Output: TryInto<usize, Error: Debug>>,
{
    fn next(self) -> Option<Self>
    where
        Self: Sized,
    {
        let mut for_step = RangeFrom { start: self.item };
        let _for_step_res = for_step.next();
        let v_next = for_step.start;
        if v_next >= self.ref_range.range.end {
            None
        } else {
            Some(Self {
                item: v_next,
                ref_range: self.ref_range,
            })
        }
    }

    fn index(&self) -> usize {
        (self.item - self.ref_range.range.start).try_into().unwrap()
    }

    fn zero_assign(&mut self) {
        self.item = self.ref_range.range.start;
    }
}

impl<T, RangeRef> CountableRangeForRef for NTDeref<RangeRef,CountableRangeStd<T>>
//CountableRangeStd<T,RangeRef,ToNewRef>
where
    RangeRef: Deref<Target = CountableRangeStd<T>>,
    RangeFrom<T>: Iterator<Item = T>,
    T: Copy + Ord + Sub<Output: TryInto<usize, Error: Debug>> + Add<Output = T>,
    usize: TryInto<T, Error: Debug>,
{
    type Item = CountableRangeStdItem<T, RangeRef>;

    fn item_first(self) -> Self::Item {
        CountableRangeStdItem {
            item: self.0.range.start,
            ref_range: self.0, //.clone()
        }
    }

    fn item_from_index(self, index: usize) -> Option<Self::Item> {
        let real_idx = index.try_into().unwrap() + self.0.range.start;
        if real_idx >= self.0.range.end {
            None
        } else {
            Some(CountableRangeStdItem {
                item: real_idx,
                ref_range: self.0, //.clone()
            })
        }
    }

    fn index_len(self) -> usize {
        (self.0.range.end - self.0.range.start).try_into().unwrap()
    }
}

struct Meme;
struct MemeItem;
impl CountableRangeItem for MemeItem {
    fn next(self)->Option<Self> where Self: Sized {
        todo!()
    }

    fn index(&self)->usize {
        todo!()
    }

    fn zero_assign(&mut self) {
        todo!()
    }
}
impl<RangeRef> CountableRangeForRef for NTDeref<RangeRef,Meme>
    where 
        RangeRef: Deref<Target = CountableRangeStd<Meme>>,
{
    type Item=MemeItem;

    fn item_first(self)->Self::Item {
        todo!()
    }

    fn item_from_index(self,index:usize)->Option<Self::Item> {
        todo!()
    }

    fn index_len(self)->usize {
        todo!()
    }
}


#[cfg(test)]
mod tests {
    use super::*;
    use std::rc::Rc;

    #[test]
    fn flexible_refs() {
        let range = CountableRangeStd { range: -2i32..2 };
        fn check<Ref: Deref<Target = CountableRangeStd<i32>>>(
            mut item: CountableRangeStdItem<i32, Ref>,
        ) {
            assert_eq!(*item, -2);
            item = item.next().unwrap(); assert_eq!(*item, -1);
            item = item.next().unwrap(); assert_eq!(*item, 0);
            item = item.next().unwrap(); assert_eq!(*item, 1);
            assert!(item.next().is_none());
        }

        // &T
        check((&range).as_nt_deref().item_first());

        let outer_item;
        {
            let rc = Rc::new(range);
            let inner_item=rc.clone().as_nt_deref().item_first();
            outer_item=rc.clone().as_nt_deref().item_first();
            check(inner_item);
        }
        // Rc<T>
        
        check(outer_item);
    }
}

Why This?

  • Wrap any ref with .as_nt_deref(), get a single blanket impl.
  • Items deref to their value (e.g., *item).
  • Works for borrows, shared ownership, etc.—no dupes.
  • Drawbacks: Crate-local newtype; consumes self (ok for factories).

Could extend to iterators or builders. Any tweaks for idiomatic Rust? Cheers!

what does that mean? particularly, what is the term "factory" referring to? can you provide a motivating example to demonstrate your use case?

the example code looks over complicated and I can't get the point of the test case. to me, it's pretty much the same functionality as std::iter::Peekable, except .peek() is replaced with the deref operator? you need a better example to show the intended use case.

3 Likes

For example , we have N Dim Array with range NDimRange{lens:[Range<isize>;DIM],...}, and a method to map between [isize;DIM] and usize, also a way to iterate it (also Im trying to make it more generic).
You need NDimRange to do that.

Will you copy NDimRange for each [isize;DIM],
or always using NDimRange to operate [isize;DIM],
or giving &NDimRange for [isize;DIM] ?
Dont forget that Rc<NDimRange> or Arc or Box might be used,
and I found a way for this, by impl<TRef:Deref<Target=NDimRange>> Blablabla for NTDeref<TRef,NDimRange>

I'm sorry about that term "factory" might be confusing.

After more try it seems better to use Deref<Target = [Range;DIM]> instead of deref self.

NDimRange< TLens:Deref<Target = [Range<isize>;DIM]> >{lens:TLens,...}

there might be some unfortunate miscommunication due to language barrior, but to me, the use case you just described is not much different than an extension trait with a blanket implementation. (maybe you have already found out?)

for example, since the trait CountableRangeForRef is local, instead of:

impl<T, RangeRef> CountableRangeForRef for NTDeref<RangeRef,CountableRangeStd<T>>
where
    RangeRef: Deref<Target = CountableRangeStd<T>>,
    //...

you can just do a blanket implementation:

impl<T, RangeRef> CountableRangeForRef for RangeRef
where
    RangeRef: Deref<Target = CountableRangeStd<T>>,
//...

besides, even if you need a newtype wrapper to workaround the orphan rule or for other reasons, you don't need the generic type parameter Target and the PhantomData in the NTDeref type, they are completely redundent. you can just use <Ref as Deref>::Target when you need the type name.


side note:

sometimes it's frustrating that you have an idea in mind but can't explain it clearly in language. this happened to everyone, not just for non native languages. don't get discouraged, the rust community are welcoming and helpful. feel free to discuss everthing rust related on this forum.

it would be great if you could reach someone who understands your idea well and can help you with the translation. or, if you don't mind share some example code or link to a repo of real world use case, that would be helpful too.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.