Skip to content

Add get and get_unchecked to *const [T] and *mut [T] #60639

Closed

Description

@oli-obk

If one has a *const [T] and wants a pointer to a specific element of that slice, one needs to go through a reference and index, or cast the pointer to *const T and offset that by the number of elements (which doesn't do a length check, so you need that, too). I propose that we add two new methods to *const [T] and *mut [T]:

impl<T> *const [T] {
    fn get(self, idx: usize) -> Option<*const T>;
    unsafe fn get_unchecked(self, idx: usize) -> *const T;
} 

get_unchecked is unsafe, because if a large index is used, one may overflow the pointer, which is UB

To make the implementation of get simpler, I propose to additionally add a len method:

impl<T> *const [T] {
    fn len(self) -> usize;
}

cc @RalfJung @shepmaster

Activity

  1. added
    T-libs-api[DEPRECATED; DO NOT USE]
    C-feature-requestCategory: A feature request, i.e: not implemented / a PR.
    on May 8, 2019
  2. shepmaster commented on May 8, 2019

    @shepmaster
    Member

    My specific case would have been for *mut [T] — is that deliberately omitted?

  3. oli-obk commented on May 8, 2019

    @oli-obk
    ContributorAuthor

    no, I'm just lazy and didn't want to type everything twice. There should be an equivalent of all the above for *mut [T]

  4. changed the title [-]add `get` and `get_unchecked` to `*const [T]`[/-] [+]Add `get` and `get_unchecked` to `*const [T]` and `*mut [T]`[/+] on May 8, 2019
  5. Stargateur commented on May 8, 2019

    @Stargateur
    Contributor

    Pardon me if It's stupid but why not just let the user go from *const [T] to &[T] ?

    fn main() {
        let x_ptr = &[1, 2, 4] as *const [_];
        
        unsafe {
            let x = &*x_ptr;
            println!("{:?}", x.get(1).map(|x| x as *const _));
            println!("{:?}", x.get_unchecked(1) as *const _);
        }
    }

    Is that really to long to write ? Also, want to ask, when do you need a *const [T] ?

  6. shepmaster commented on May 8, 2019

    @shepmaster
    Member

    The original context was this code:

    use std::collections::BTreeSet;
    
    fn uniq_refs<'i, 'd: 'i, T>(
        data: &'d mut [T],
        indices: &'i BTreeSet<usize>,
    ) -> impl Iterator<Item = &'d mut T> + 'i {
        let in_bounds_indices = indices.range(0..data.len());
    
        in_bounds_indices.map(move |&i| {
            // I copied this from a Stack Overflow answer 
            // without reading the text that explains why this is safe
            unsafe {
                let r = data.get_unchecked_mut(i);
                let p: *mut T = r;
                &mut *p
            }
        })
    }

    Miri detects that this usage triggers undefined behavior:

    use std::iter::FromIterator;
    
    fn main() {
        let mut scores = vec![1, 2, 3];
        let indices = BTreeSet::from_iter(vec![0, 2]);
    
        let selected_scores: Vec<_> = uniq_refs(&mut scores, &indices).collect();
        for score in selected_scores {
            *score += 1;
        }
    }

    I wanted to make use of get_unchecked_mut, but that function requires unique access to data. That's invalid here because there's an outstanding mutable reference to a T from inside the slice. The non-UB form of this involves getting a raw pointer to the data and performing offsets:

    fn uniq_refs<'i, 'd: 'i, T>(
        data: &'d mut [T],
        indices: &'i BTreeSet<usize>,
    ) -> impl Iterator<Item = &'d mut T> + 'i {
        let start = data.as_mut_ptr();
        let in_bounds_indices = indices.range(0..data.len());
        in_bounds_indices.map(move |&i| unsafe { &mut *start.add(i) })
    }

    With the proposed functions, that code could look like

    fn uniq_refs<'i, 'd: 'i, T>(
        data: &'d mut [T],
        indices: &'i BTreeSet<usize>,
    ) -> impl Iterator<Item = &'d mut T> + 'i {
        let start = data as *mut [T];
        let in_bounds_indices = indices.range(0..data.len());
        in_bounds_indices.map(move |&i| unsafe { &mut *data.get_unchecked(i) })
    }
  7. RalfJung commented on May 9, 2019

    @RalfJung
    Member

    Yeah, this seems reasonable. However, I think it should be seen in the context of our general need for a better story for raw pointers.

  8. scottmcm commented on May 12, 2019

    @scottmcm
    Member
    impl<T> *const [T] {
        fn get(self, idx: usize) -> Option<*const T>;
    }

    Wouldn't that need to be unsafe for the same reason as offset?

  9. oli-obk commented on May 12, 2019

    @oli-obk
    ContributorAuthor

    Since it would be bounds-checked, you can't overflow, so get should be safe

  10. oli-obk commented on May 12, 2019

    @oli-obk
    ContributorAuthor

    Ah no, you're right. One can create a wrong slice that would overflow for some indices...

  11. RalfJung commented on May 12, 2019

    @RalfJung
    Member

    It could use wrapping_offset, though. Then it could be safe.

  12. Stargateur commented on May 12, 2019

    @Stargateur
    Contributor

    just to note that in C, it's UB to create a out of bound pointer of more than len, for exemple:

    char *ptr = malloc(42); // assert no null
    char *ub = ptr + 43;
    char *also_ub = ptr - 1;

    Look like Rust allow this, but use raw pointer is often use with C binding, I just through this could be useful to know.

  13. hanna-kruppe commented on May 12, 2019

    @hanna-kruppe
    Contributor

    It could use wrapping_offset, though. Then it could be safe.

    But then people would have a performance incentive to not use the clearer and more convenient method. And what for? If one uses this method they'll almost always go on to access memory through the resulting pointer, and then they'd almost certainly have UB anyway if the index is too large or goes otherwise out of bounds. While wrapping_offset does allow "allocation crossing" with a suitably crafted offset, this is extremely rare and so I don't see why we should prioritize it in a convenience method.

  14. RalfJung commented on May 12, 2019

    @RalfJung
    Member

    But then people would have a performance incentive to not use the clearer and more convenient method.

    If they want the performance, wouldn't they use get_unchecked anyway? Seems like if you care about whatever LLVM gets from knowing there is no overflow, you'll also want no bounds check.

    Put differently, what is even the point of having get if it is not safe?

  15. 4 remaining items

  16. RalfJung commented on May 24, 2019

    @RalfJung
    Member

    It's a raw pointer, I don't think we are making any such requirements. Both pointer and integer can be just about anything that a *mut T or usize can be.

  17. RalfJung commented on Mar 25, 2020

    @RalfJung
    Member

    So, in terms of implementing this as an unstable feature... the main problem is that we want these to be inherent methods on *const [T], right? AFAIK this needs some rustc magic and lang items as those types are not defined in any crate, and thus cannot usually get inherent methods?

  18. oli-obk commented on Mar 25, 2020

    @oli-obk
    ContributorAuthor

    Yea, we need to add the same kind of magic that the integer types have in

    rust/src/libcore/num/mod.rs

    Lines 2380 to 2381 in 1572c43

    #[lang = "i8"]
    impl i8 {

    Though in this case it may be more difficult due to the involved generics, so you should look at the implementation of the magic for slices for inspiration:

    #[lang = "slice"]
    #[cfg(not(test))]
    impl<T> [T] {

  19. RalfJung commented on Apr 15, 2020

    @RalfJung
    Member

    #71082 does all the hard parts here -- now adding more raw slice methods (like indexing) is a libcore-only patch, rustc does not need any further changes.

  20. RalfJung commented on Jul 5, 2020

    @RalfJung
    Member

    #73986 should resolve this.

  21. added 7 commits that reference this issue on Jul 13, 2020
  22. WaffleLapkin commented on May 30, 2021

    @WaffleLapkin
    Member

    The checked version,

    impl<T> *const [T] {
        fn get(self, idx: usize) -> Option<*const T>;
    }

    wasn't implemented. Why?

  23. RalfJung commented on May 30, 2021

    @RalfJung
    Member

    As far as I am concerned, mostly due to lack of imagination -- I figured if you'd want checks you'd use references, not raw pointers. The return type Option<*const T> is also strange, note that this one cannot be enum-niche-optimized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-feature-requestCategory: A feature request, i.e: not implemented / a PR.T-libs-api[DEPRECATED; DO NOT USE]

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions