Tuple Slices


#1

I believe these transmutes are currently UB, but it would be nice if tuple slicing (or at least prefixing) were legal. So, is there any concrete reason not to allow this?

use std::mem::transmute;
fn main() {
    let tuple = ("str", 1u32, 2u64);
    unsafe {
        let prefix1: &(&str,) = transmute(&tuple);
        let prefix2: &(&str, u32) = transmute(&tuple);
        let suffix: &(u32, u64) = transmute(&tuple.1);

        println!("{:?}", prefix1);
        println!("{:?}", prefix2);
        println!("{:?}", suffix);
    }
}

#2

Especially the suffix one is problematic. (u32, u64) might be layed out differently in memory than (something, u32, u64) because of alignment.

When reading your post, I’m mostly just thinking “no no no.” Did you have a usecase in mind for this?


#3

But… lisp! Really, I wanted to be able to query BTreeSets/BTreeMaps by tuple prefix (and needed a way to borrow tuple prefix’s from tuples) but, after thinking about it, I’ve come to the conclusion that there are definitely better ways to go about this…

After thinking about it, it’s probably best to just write (((A, B), C), D) when one needs to be able to do things like this.


#4

This seems like a good option. Also, do you really need to borrow? I’m not sure what you’re storing in your tuples, but if like in your example it’s immutable references and integers, you’ll get the same or better performance when just copying those values in your prefixes. You can easily implement a newtype and prefix-newtypes for your BTrees that have the right Ord implementations.


#5

I was trying to work around some limitations of BTreeSet and the Ord trait. Basically,

  1. Ord only allows comparing T to T.
  2. BTreeSet::<T>::range allows querying by any type Q such that T: Borrow<Q>, Q: Ord (i.e., Q can be borrowed from T and Q implements Ord).

Given BTreeSet<(A, B, C)> I wanted to query for all (a, b, *). Ord can’t be used to compare (A, B) to (A, B, C) (not the same type) so I needed to be able to “borrow” my query type from (A, B, C).

However, the better way to do this would be to change the range method to take a some type implementing a RangeQuery trait:

trait RangeQuery<T> {
    fn cmp(&self, &T) -> Ordering;
}

(like Ord but T doesn’t have to be Self).


#6

I think something like this could work for your case:

struct A;
struct B;
struct C;
enum InnerTuple {
    Full(A,B,C),
    Prefix2(A,B),
    Prefix1(A),
}
struct MyTuple {
    inner: InnerTuple
}
impl MyTuple {
    fn new(a: A,b: B,c: C) -> MyTuple {
        MyTuple{inner:InnerTuple::Full(a,b,c)}
    }
}
impl ::std::borrow::Borrow<InnerTuple> for MyTuple {
    fn borrow(&self) -> &InnerTuple {
        &self.inner
    }
}

#7

Enums work (that’s what I tried first) but force me to store an extra word for the tag. Really, I had (A, B) and needed to “borrow” A so I just did the following:

#[derive(Ord, PartialOrd, Eq, PartialEq)]
struct Pair<A, B>(A, B);
#[derive(Ord, PartialOrd, Eq, PartialEq)]
struct Left<A>(A);
impl Borrow<Left<A>> for Pair<A, B> {
    fn borrow(&self) -> &Left<A> {
        unsafe {
            // UB? The standard library does it...
            mem::transmute(&self.0)
        }
    }
}

#8

relevant


#9

Thanks!


#10

Does Rust even guarantee that the elements of a tuple won’t be reorder to reduce padding?

I don’t think so, and I think that reordering the elements of a tuple to reduce padding, and thus tuple size is an important optimization.

I don’t see how tuple slicing can work in this case.


#11

@gnzlbg there is a lot of discussion on that point and related ones on the linked thread (and others linked from it in turn)