Brainstorming: Newtypes for DSTs without needing `unsafe`

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?

1 Like

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?

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.

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.

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, …

1 Like

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

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).

1 Like

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 }
}
4 Likes

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) }.

1 Like

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.

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 }
2 Likes

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

1 Like

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".

1 Like

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 Announcing Rust 1.15.1 | Rust Blog

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.

1 Like

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?

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.