Brainstorming: Newtypes for DSTs without needing `unsafe`


#1

Context: Some time ago I contemplated a slice adapter for reversing, as a way to do negative indexing without the negatives. I finally got around to making a crate for it, and it worked pretty well.

Problem: I really wish I didn’t need any unsafe code, but didn’t see any way around this bit:

(There are other ways I could write the code, but they’re all unsafe.)

Question: Any good ideas for language or library changes we might do to make unsafe unnecessary there?


#2

#3

Just quick thoughts.

If you are handling non-DSTs, you are looking at enums (converting to another type intrinsically)

The enums for DSTs are trait objects, so maybe you can use trait object here?


#4

I don’t think I need custom DSTs here, @ubsan. I’m completely happy with the normal, your-last-field-DST-makes-you-a-DST support that we already have. (I don’t need different metadata from what the normal slice DST already has.) There’s just no constructor I can use.


#5

I think you can let it return RevSliceRef<'a,T> and RevSliceMut<'a,T> instead? then you can just let it hold a reference to the original array. No need to use unsafe code.


#6

I could, but then it doesn’t have the &mut RevSlice<T> -> &RevSlice<T> coercion, I can’t return it from Index/IndexMut, it doesn’t have auto-reborrow, I have to duplicate the methods because I can’t Deref, …


#7

What about simply define RevArray<T> and implement let rev work like a constructor? Then you can implement Index/IndexMut for RevArray<T>.


#8

My ref-cast crate provides a safe abstraction for those casts.

#[derive(RefCast)]
#[repr(transparent)]
pub struct RevSlice<T>([T]);

...
fn rev(&self) -> &RevSlice<T> {
    RevSlice::ref_cast(self)
}

Separately, I believe the transmutes in your current implementation are currently both undefined behavior.
https://github.com/scottmcm/rev_slice/issues/1
The RefCast derive catches missing repr(C) / repr(transparent).


#9

Assuming we settle on guaranteeing the same memory layout for newtype structs as their one field (rust-rfcs/unsafe-code-guidelines#31), would it be possible to allow a safe `as` cast from &T to &U where U is a struct containing a single field of type T, so long as the field’s visibility makes it visible to the code containing the cast?

struct U {
    t: T,
}

fn demo(t: &T) -> &U {
    t as &U
}

Alternatively, the following would make it clearer that visibility is required.

fn demo(t: &T) -> &U {
    &U { t: *t }
}

#10

Ooh, this is really interesting. It’s like adding literals to our existing place expressions, and could easily extend to similar cases that currently need unsafe. For example, imagine something like this:

fn demo2<T>(t: &T) -> &[T; 1] {
    &[*t]
}

In some ways it also feels like a generalization of rvalue static promotion: &[4] is 'static because it’s like { static x: i32 = 4; demo2(&x) }.


#11

Hopefully it could also support e.g. &U { t: *t, marker: PhantomData }.

Unfortunately there may be an ambiguity in the mutable case:

struct S(i32);

fn f(x: &mut i32) {
    let s = &mut S(*x);
    s.0 = 1;
}

fn main() {
    let mut x = 0;
    f(&mut x);
    println!("{}", x);
}

This compiles today and prints 0. If &mut S(*x) where made equivalent to mem::transmute::<&mut i32, &mut S>(x) then this would begin printing 1 instead.

I’ve heard whispers that people are souring on `as`. Does that mean using `as` here is out of the question? It has the benefit of not conflicting with existing compilable code.


#12

Here is a somewhat more confusing but unambiguous spin on the earlier place expressions suggestion. This is analogous to reference patterns like let &t = t.

fn demo(t: &T) -> &U {
    &U { &t: t }
}

The newtype case is unfortunate:

struct U(T);

...
&U { &0: t }

#13

Interesting. This feels vaguely similar to “match ergonomics”. (Incidentally, do we have a… better name for that?)


#14

I’ve completely failed to find the thread/post where this came up, but I’m sure I’ve seen a few people suggest names like “adaptive binding modes”.


#15

As one of those people, I’m only soured on as when it does things that are different but look the same, in a way that accidentally doing the wrong thing is easy. Like where as *mut T might be an innocuous coercion from &mut T, but also might be a contributing factor to the bug that necessitated https://blog.rust-lang.org/2017/02/09/Rust-1.15.1.html

I’m unsure whether this would be one of the cases I dislike. I guess that since it’s ref-to-ref it would be safe and pretty limited, and there are no other to-ref casts in as today, so it’s probably fine.


#16

Maybe I’m missing something, but can this not be guaranteed by annotating the newtype with #[repr(transparent)] which was stabilized in Rust 1.28.0?