How to share resources without exposing Rc in the API?

Viewed 101

I'm working on a geometry crate and I thought in order for things to work nicely I should use Rc when referencing shared points. For example:

struct Point {
    point: Vec4,
}

struct Line {
    points: [Rc<Point>; 2],
}

struct Tri {
    points: [Weak<Point>; 3],
    edges: [Rc<Line>; 3],
}

struct Tetra {
    points: [Weak<Point>; 4],
    edges: [Weak<Line>; 6],
    faces: [Rc<Tri>; 4],
}

This makes sense as it seems to be the most efficient way to keep track of the components of a simplex. The problem is in the API. I don't want to expose this Rc internal, and I'd like it if I can just pass references in. For example, something like this:

let points = [Point::new(p0), Point::new(p1), Point::new(p2), Point::new(p3)];
let tris = [
    Tri::new(&points[0], &points[1], &points[2]),
    Tri::new(&points[2], &points[3], &points[0])
];

Etc. Without the user having to explicitly deal with Rc themselves.

What sort of pattern do we typically see here?

1 Answers

If you want resources to be shared without the user managing the Rcs themselves, you create your internal structure as normal and create wrappers around the Rcs for your public interface.

Keep your structure as-is but preferably with a naming scheme indicating that they're internal (often Inner or Impl suffixed):

struct PointInner {
    point: Vec4,
}

struct LineInner {
    points: [Rc<PointInner>; 2],
}

struct TriInner {
    points: [Weak<PointInner>; 3],
    edges: [Rc<LineInner>; 3],
}

struct TetraInner {
    points: [Weak<PointInner>; 4],
    edges: [Weak<LineInner>; 6],
    faces: [Rc<TriInner>; 4],
}

Then create your user-facing types that are just wrappers around the Rcs with a nice facade dealing with the links internally (you can use an inner field as I've done or use new type structs):

pub struct Point {
    inner: Rc<PointInner>,
}

pub struct Line {
    inner: Rc<LineInner>,
}

pub struct Tri {
    inner: Rc<TriInner>,
}

pub struct Tetra {
    inner: Rc<TetraInner>,
}

impl Point {
    pub fn new(point: Vec4) -> Point {
        Point {
            inner: Rc::new(PointInner { point }),
        }
    }
}

impl Tri {
    pub fn new(p1: &Point, p2: &Point, p3: &Point) -> Tri {
        Tri {
            inner: Rc::new(TriInner {
                points: [
                    Rc::downgrade(&p1.inner),
                    Rc::downgrade(&p2.inner),
                    Rc::downgrade(&p3.inner),
                ],
                edges: [
                    Rc::new(LineInner {
                        points: [Rc::clone(&p1.inner), Rc::clone(&p2.inner)],
                    }),
                    Rc::new(LineInner {
                        points: [Rc::clone(&p2.inner), Rc::clone(&p3.inner)],
                    }),
                    Rc::new(LineInner {
                        points: [Rc::clone(&p3.inner), Rc::clone(&p1.inner)],
                    }),
                ],
            }),
        }
    }
}

With this your desired code works without changes. See it working on the playground.

Related