Skip to content

Add more clarifying examples to Sculptor documentation#251

Merged
lloydmeta merged 6 commits into
lloydmeta:masterfrom
Lindamust:master
Jun 19, 2026
Merged

Add more clarifying examples to Sculptor documentation#251
lloydmeta merged 6 commits into
lloydmeta:masterfrom
Lindamust:master

Conversation

@Lindamust

Copy link
Copy Markdown
Contributor
/// Trait for borrowing multiple HList elements by type
pub trait Projector<Targets, Indices> {
    /// The projected view into the list
    type Projection<'a>
    where
        Self: 'a,
        Targets: 'a;

    /// Borrow multiple elements by type from an HList.
    fn project(&self) -> Self::Projection<'_>;
}

Usage example:

fn main () {
    use frunk_core::{hlist, HList, hlist_pat};
    let h = hlist![123u32, "hello world", true];
    type S = HList![bool, u32];

    let projection: HList![&bool, &u32] = h.project::<S, _>();
    let hlist_pat![bool_ref, u32_ref] = projection;
    assert_eq!(bool_ref, &true);
    assert_eq!(u32_ref, &123);
}

Description:
HList projection is an operation that views a heterogeneous list through a type-level “lens” of sorts, producing a new HList of shared references to selected elements. This is similar to how Sculptor is like a generalized version Plucker. Projector aims to be a more generalized version of Selector. It works by recursively calling get on the target types and accumulating their references in a HList. Though, this does come at a bit of a caveat...

We are calling get multiple times, which is fine because its a shared reference. The problem is that this locks us out of a possible fn project_mut in the future because Rust disallows multiple mutable references. Though, there could create a separate ProjectMut trait in the future, but the question of how to safely handle multiple dangling mutable references is still at large. I have documented a few possible workarounds in the footnotes of my crate: https://crates.io/crates/rust-hlist-projection/0.1.0

There is also another factor to consider. Right now the associated type Projection<'a> is generic over some lifetime 'a, so the trait as a whole is inherently not dyn compatible. This could be a limitation in some use cases, so a potential solution might be to move the lifetime to the trait generics. I didn't opt for it initially as it might not fit the style considering that Selector doesn't specify a lifetime on the trait generics even though its function is to borrow values.

Motivation:
I'm fairly new to functional programming, so when I stumbled upon your article on BeachApe I was quite intrigued by the type recursion. Looking into the code base I noticed that there is a missing link of abstraction of sorts. As mentioned before, one could think that Sculptor is a generalized version of Plucker. However, I couldn't find any clean or explicitly documented "generalized" Selector, which is where this PR comes into play. I hope some people find this interesting and useful as I don't really have any decent real-world use cases for something like this yet, though having the functionality available for the future wouldn't be a bad thing.

Also:
Is project a good name for this? I wouldn't mind if it was changed.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an HList “projection” API to borrow multiple elements by type (without moving them), returning a new HList of shared references. This complements existing selection/sculpting capabilities by providing a generalized multi-get operation.

Changes:

  • Introduce HCons::project inherent method for projecting an HList into a target “shape” as shared references.
  • Add the Projector<Targets, Indices> trait with recursive implementations built on top of Selector.
  • Add unit tests covering simple, multi-type, reordered, and “borrow doesn’t move” scenarios.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread core/src/hlist.rs Outdated
Comment on lines +330 to +334
/// `project` allows us to select many components of a structure without moving or removing them,
/// but instead borrowing them from the original.
/// That is, provided that the requested types are contained wihtin the currnt HList.
///
/// It also does not require the specified types to be in the same order as they are in the current hlist.
Comment thread core/src/hlist.rs Outdated
Comment thread core/src/hlist.rs Outdated
Comment on lines +2200 to +2204
// fn project_non_existant() {
// let h = hlist![1u32, "hello world", 42i64, true];
// type S = HList![i64, f32]; // <--- There is not 'f32' in our example HList

// let projection = h.project_ref_ext::<S, _>(); // <--- therefore this method will not work
Comment thread core/src/hlist.rs Outdated
Comment on lines +2178 to +2182
let projection = h.project::<S, _>();
let hlist_pat![usize_ref, isize_ref] = projection;

assert_eq!(*usize_ref, 1u32);
assert_eq!(*isize_ref, 42i64);
Comment thread core/src/hlist.rs Outdated
Comment on lines +2191 to +2195
let projection = h.project::<S, _>();
let hlist_pat![isize_ref, usize_ref] = projection;

assert_eq!(*isize_ref, 42i64);
assert_eq!(*usize_ref, 1u32);
Comment thread core/src/hlist.rs Outdated
Comment on lines +2227 to +2234
let projection = h.project::<S, _>();
let hlist_pat![string, bool, int64, un32, float64] = projection;

assert_eq!(*string, "hello world".to_string());
assert_eq!(*bool, true);
assert_eq!(*int64, 99i64);
assert_eq!(*un32, 1u32);
assert_eq!(*float64, 12f64);
@lloydmeta

Copy link
Copy Markdown
Owner

Very cool stuff ; I'm intrigued and think this would fit in the library yes.

I'm going to take a closer look when I have some more time, and maybe have a think about the name (I've tried hard to pick names that are extremely concrete, and obvious), though at the moment I don't have anything better than what you came up with :) thanks for the PR!

@lloydmeta

Copy link
Copy Markdown
Owner

Sorry that I didn't remember this (been a while since I've done much work here) but I think this accomplishes something similar and is supported today (takes an extra .to_ref() step which I had forgotten existed...):

Added in hlist.rs

    #[test]
    fn test_sculpt_after_ref() {
        let non_ref = hlist![9000, "joe", 41f32];
        let h = non_ref.to_ref();
        let (reshaped, remainder): (HList!(&f32, &i32), _) = h.sculpt();
        assert_eq!(reshaped, hlist![&41f32, &9000]);
        assert_eq!(remainder, hlist![&"joe"])
    }

    #[test]
    fn test_sculpt_after_ref_yours() {
        let non_ref = hlist![123u32, "hello world", true];
        let h = non_ref.to_ref();
        let (reshaped, remainder): (HList!(&bool, &u32), _) = h.sculpt();
        assert_eq!(reshaped, hlist![&true, &123]);
        assert_eq!(remainder, hlist![&"hello world"])
    }

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@Lindamust

Copy link
Copy Markdown
Contributor Author

Ah I see... My main concern was whether or not the original non_ref would be moved.
Like being able to take out the references while still being able to use the original at a later point.

@lloydmeta

lloydmeta commented Jun 18, 2026

Copy link
Copy Markdown
Owner

Ah I see... My main concern was whether or not the original non_ref would be moved. Like being able to take out the references while still being able to use the original at a later point.

    #[test]
    fn test_sculpt_after_ref_yours() {
        let non_ref = hlist![123u32, "hello world", true];
        let h = non_ref.to_ref();
        let (reshaped, remainder): (HList!(&bool, &u32), _) = h.sculpt();
        assert_eq!(reshaped, hlist![&true, &123]);
        assert_eq!(remainder, hlist![&"hello world"]);
        assert_eq!(non_ref.head, 123u32);
    }

I think that works, because to_ref() takes a reference

/// An alternative to AsRef that does not force the reference type to be a pointer itself.
///
/// This lets us create implementations for our recursive traits that take the resulting
/// Output reference type, without having to deal with strange, spurious overflows
/// that sometimes occur when trying to implement a trait for &'a T (see this comment:
/// <https://github.com/lloydmeta/frunk/pull/106#issuecomment-377927198>)
///
/// This functionality is also provided as an inherent method [on HLists] and [on Coproducts].
/// However, you may find this trait useful in generic contexts.
///
/// [on HLists]: ../hlist/struct.HCons.html#method.to_ref
/// [on Coproducts]: ../coproduct/enum.Coproduct.html#method.to_ref
pub trait ToRef<'a> {
type Output;
fn to_ref(&'a self) -> Self::Output;
}

Just now, I was confused by this as well (why did I name it **To**Ref even though it doesn't consume the original.... and it's because AsRef forces the returned thing to be a reference. It's not great and maybe I could have named it better 🤦🏼

@Lindamust

Copy link
Copy Markdown
Contributor Author

I guess this is bit of a facepalm for both of us... Though the exposure to type-level programming has been a great experience so far. Therefore, I think moving forward it would be nice to have something like this explicitly documented as I think its actually quite a nice feature.

@lloydmeta

Copy link
Copy Markdown
Owner

I guess this is bit of a facepalm for both of us... Though the exposure to type-level programming has been a great experience so far. Therefore, I think moving forward it would be nice to have something like this explicitly documented as I think its actually quite a nice feature.

++ happy to have this documented; there's so many ways to compose this stuff that it certainly can be good to have it written down if you consider it useful.

@Lindamust

Copy link
Copy Markdown
Contributor Author

Alright I've removed the Projector trait and replaced it with extra documentation on the Sculptor method. ToRef achieves the same effect and ToMut also allows for the mutable projections I was concerned about.

@Lindamust Lindamust changed the title feature: HList Reference Projection Add more clarifying examples to Sculptor documentation Jun 19, 2026

@lloydmeta lloydmeta left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, just a tiny fix.

Comment thread core/src/hlist.rs Outdated
Lindamust and others added 3 commits June 19, 2026 17:23
fix typo

Co-authored-by: Lloyd <lloydmeta@users.noreply.github.com>
@lloydmeta lloydmeta merged commit 36f7a15 into lloydmeta:master Jun 19, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants