Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Investigate ZST-slices #5

Closed
oberien opened this issue Jul 16, 2019 · 11 comments
Closed

Investigate ZST-slices #5

oberien opened this issue Jul 16, 2019 · 11 comments

Comments

@oberien
Copy link
Owner

oberien commented Jul 16, 2019

How does concat_slice(&[(); isize::max_value()][..], &[(); isize::max_value()][..]) work? How should it be handled? We should add a comment somewhere once we know what it does / have decided on what to do.
@HeroicKatora

@HeroicKatora
Copy link
Collaborator

HeroicKatora commented Jul 16, 2019

It should be completely mostly safe to simply pretend to have extended the first slice by the length of the second (edit: if it is initially non-empty). All invariants required seem satisfied:

  • the region (which has no length) is valid for the lifetime of the result.
  • the pointer of a.as_ptr() is non-null and aligned by construction
  • the length in bytes is smaller than isize::max_value()

For a ZST we can no longer assume that the length is <= isize::max_value() and so their summed length may overflow usize. But we don't may not need the slices to be adjacent to lengthen either one to the total length. This may make the result confusing if the method works even for slices that are not located at the same address but not UB.

@HeroicKatora
Copy link
Collaborator

Interesting, the compiler does not like it though: play

@HeroicKatora
Copy link
Collaborator

HeroicKatora commented Jul 16, 2019

Conjuring the slice of such a length from unsafe works fine for the rest though:

static S: &() = &();

fn main() {
    let s: &[()] = unsafe {
        core::slice::from_raw_parts(S, usize::max_value())
    };
    println!("{:?}", s[usize::max_value() - 1]);
}

play

@HeroicKatora
Copy link
Collaborator

HeroicKatora commented Jul 16, 2019

Oh, using a Vec<()> and resize_with/with_capacity works fine as well but only in Release as it otherwise probably iterates till the end of days. This means it is actually safe to create slices with the length of usize::max_value() for at least some ZST. (And I'm left wondering if slice::repeat will be properly optimized here. edit: yes).

fn main() {
    let mut s: Vec<()> = Vec::with_capacity(usize::max_value());
    s.resize_with(usize::max_value(), <()>::default);
    println!("{:?}", s[usize::max_value() - 1]);
}

play

@HeroicKatora
Copy link
Collaborator

This leaves open the question whether the new slice is allowed to point to a.len() + b.len() instances of T. I think this is the case when either slice has a length different from 0, there is one instance that can be referenced and all other refer to the same instance. When neither has a length different from 0 than the result does not either.

@HeroicKatora
Copy link
Collaborator

Even if memory safe, concatenating ZST slices is outright weird.

let a: &[()] = [(); 4];
// The difference between these can not be observed within `concat_slice`:
concat_slice(&a[..2], &a[2..]);
concat_slice(&a[..2], &a[..2]);

Meaning, even if we were to restrict concatenating ZST slices to a.as_ptr() == b.as_ptr() one could generate arbitrarily long slices from a single input slice of length >= 1.

@HeroicKatora
Copy link
Collaborator

As pointed out to me in this thread it may be invalid to create references to an uninhabited ZST. In that case, we must ensure not to create a slice of length > 0 out of the pointer from a slice of length 0 since the latter could be valid even when the former is not. This aligns with the comment above of choose an input slice of non-zero length when the output requires it to not create references from nothing.

@oberien
Copy link
Owner Author

oberien commented Jul 17, 2019

playpen

Arrays, vectors and slices have special handling of ZSTs. For example a Vec of ZSTs always points to 0x1. Multiple arrays point to their respective local stack location, but they are merged in release, resulting in different pointers on debug and the same pointer on release.

@HeroicKatora
Copy link
Collaborator

HeroicKatora commented Jul 17, 2019

Iirc, allocated slices (vec and boxed, etc.) will point to NonNull::dangling (which is the smallest, non-null and aligned pointer for the type, literally mem::align_of::<T>() as *mut T) as will any allocation of size 0 e.g. as here for Vec but that is of course not guaranteed but merely convention.

@HeroicKatora
Copy link
Collaborator

HeroicKatora commented Jan 28, 2020

This question got answered:

rust-lang/unsafe-code-guidelines#168 (comment)

[..] I am sure the function presented here is safe. No need to make the question more complicated. :)

According to the definition of aliasing rules and because shared references are Copy it is totally safe to duplicate them by elongating a non-mutable slice reference provided it has initially been already non-empty. And since they are not allocated in any memory anyways, they are also the only types where no unsafe attribute of the function itself is necessary.

@oberien
Copy link
Owner Author

oberien commented Feb 5, 2020

Fixed by #7. Added documentation in #9.

@oberien oberien closed this as completed Feb 5, 2020
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

No branches or pull requests

2 participants