Given an implementation of a heterogeneous list in rust
(for example like the one from frunk
), how can I obtain a reference to an element within the list without knowing the concrete type of the element? The length of the list is known statically, but cannot be hard-coded as numeric literal.
I have tried to obtain the individual elements by popping each element off the list sequentially (as shown in the code example) and by writing an indexing method that accepts a usize
index as argument. Neither attempts even compile. The posted example is what I would like to do.
pub trait HList: Sized {
const LEN: usize;
type Head;
type Tail: HList;
fn push<E>(self, element: E) -> Element<E, Self> {
Element { head: element, tail: self }
}
fn pop(self) -> Option<(Self::Head, Self::Tail)>;
fn len(&self) -> usize {
Self::LEN
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
pub struct Element<H, T> {
pub head: H,
pub tail: T,
}
impl<H, T: HList> HList for Element<H, T> {
const LEN: usize = 1 + <T as HList>::LEN;
type Head = H;
type Tail = T;
fn pop(self) -> Option<(H, T)> {
Some((self.head, self.tail))
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct End;
impl HList for End {
const LEN: usize = 0;
type Head = End;
type Tail = End;
fn pop(self) -> Option<(Self::Head, Self::Tail)> {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pop_two_for() {
let h = End
.push(0usize)
.push(String::from("Hello, World"));
fn eval<H: HList>(l: H) {
for _ in 0usize..H::LEN {
let (e, l) = l.pop().unwrap();
// Do work with `e`.
}
}
eval(h);
}
}
I have found a reasonable recursive solution, but it only works out if I can modify the HList
trait based on the trait requirements of the work to be done. This is fine for my use case, even if it's not as generic as I would have liked it to be.
use std::fmt::Debug;
pub trait MyCustomTrait<'a>: Debug {}
impl<'a> MyCustomTrait<'a> for () {}
impl<'a> MyCustomTrait<'a> for usize {}
impl<'a> MyCustomTrait<'a> for String {}
pub trait HList: Sized {
const LEN: usize;
type Head: for<'a> MyCustomTrait<'a>;
type Tail: HList;
fn push<E>(self, element: E) -> Element<E, Self>
where
E: for<'a> MyCustomTrait<'a>,
{
Element::new(element, self)
}
fn head(&self) -> &Self::Head;
fn tail(&self) -> &Self::Tail;
fn len(&self) -> usize {
Self::LEN
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
pub struct Element<H, T>(H, T);
impl<H, T: HList> Element<H, T> {
pub fn new(head: H, tail: T) -> Self {
Element(head, tail)
}
}
impl<H, T> HList for Element<H, T>
where
H: for<'a> MyCustomTrait<'a>,
T: HList,
{
const LEN: usize = 1 + <T as HList>::LEN;
type Head = H;
type Tail = T;
fn head(&self) -> &Self::Head {
&self.0
}
fn tail(&self) -> &Self::Tail {
&self.1
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct End;
impl HList for End {
const LEN: usize = 0;
type Head = ();
type Tail = End;
fn head(&self) -> &Self::Head {
&()
}
fn tail(&self) -> &Self::Tail {
&End
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn artbitrary_recursive() {
let h = End
.push(0usize)
.push(String::from("Hello, World"));
fn eval<H: HList>(list: &H) {
if H::LEN > 0 {
let head = list.head();
// Do work here, like print the debug representation of `head`.
eprintln!("{:?}", head);
eval(list.tail());
}
}
eval(&h);
assert!(false);
}
}