-
Notifications
You must be signed in to change notification settings - Fork 770
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
add &ReadOnlyCell<T> conversions from &T and &Cell<T> #3580
Conversation
7895a37
to
a37bd63
Compare
Is there any practical use for these conversions? I'm not sure :) But they're spiritually similar to the `Cell::from_mut` conversion in the standard library. This makes it possible to write a function taking `&ReadOnlyCell<T>` if you only need to copy the `T` and you don't need the guarantee that it won't change from one read to the next (which it could, if other `&Cell<T>` references exist pointing to the same value). With these conversions in place, the relationships between the different reference types look like this: ``` &mut T / \ coerce Cell::from_mut / \ &T &Cell<T> \ / ReadOnlyCell::from_ref ReadOnlyCell::from_cell \ / &ReadOnlyCell<T> ``` This sort of thing wasn't supported by Miri until recently, but the new Tree Borrows model supports it. The new test currently fails Miri by default: ``` $ cargo +nightly miri test test_read_only_cell_conversions ... error: Undefined Behavior: trying to retag from <747397> for SharedReadWrite permission at alloc222352[0x0], but that tag only grants SharedReadOnly permission for this location ``` But it passes with `-Zmiri-tree-borrows`: ``` $ MIRIFLAGS="-Zmiri-tree-borrows" cargo +nightly miri test test_read_only_cell_conversions ... test buffer::tests::test_read_only_cell_conversions ... ok ```
a37bd63
to
779ee45
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, this is definitely food for thought. I think the question about ReadOnlyCell
in the standard library is: under what circumstances is it actually useful? It's only meaningful under a reference indirection, because you need other references which can write to exist for the interior mutability to be a concern.
/// Converts `&T` to `&ReadOnlyCell<T>`. | ||
#[inline] | ||
pub fn from_ref(t: &T) -> &ReadOnlyCell<T> { | ||
// Safety: ReadOnlyCell is repr(transparent), and &ReadOnlyCell<T> is strictly less capable | ||
// than &T. | ||
unsafe { &*(t as *const T as *const ReadOnlyCell<T>) } | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This... is interesting. Initially I thought that T: ?Sized
is incorrect, because my intuition was that UnsafeCell<str>
(for example) is not meaningful. But, it looks like it is allowed, and I guess it's a way of denoting that the underlying str contents may have interior mutability?
I wonder, a longstanding ergonomics question for PyO3 buffers has been the fact that as_slice
returns Option<&[ReadOnlyCell<T>]>
as a slice-of-cells. I wonder, should we instead be returning Option<&ReadOnlyCell<[T]>>
and adding some helpful slice operations over the top?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah some parallel to the standard library's https://doc.rust-lang.org/std/cell/struct.Cell.html#method.as_slice_of_cells might make sense here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, I think that furthers the case that really PyBuffer
's slices should be Cell<[T]>
/ ReadOnlyCell<[T]>
.
It's hard to imagine needing this in a pure Rust codebase, because the solution to your problems is probably to just be better organized with mutability and borrowing. But for all the same reasons PyO3 has to deal with it, I expect similar problems to come up around any callback-heavy FFI boundary. I had to deal with this in the I could also imagine the standard library providing some sort of |
Definitely one observation here is that with an external synchronization mechanism (probably a lock) to prevent foreign writes then the kind of casting you do in See also https://alexgaynor.net/2022/oct/23/buffers-on-the-edge/ - @alex has wanted improvements for a long time. So if we can encourage a good ( |
I am sorry for being late to the discussion but I nevertheless wanted to add my thoughts here:
|
Very late reply, I agree with @adamreichold here. I also saw a little while ago a brief PEP draft which would add some notion of locking to Python's buffer protocol: https://discuss.python.org/t/pep-draft-safer-mutability-semantics-for-the-buffer-protocol/42346 For the sake of trying to tidy up PyO3's issue tracker I am going to close this PR for now. The record of discussion here is useful but I think we won't move forward until at least we understand what Python 3.13 support looks like in PyO3 and maybe also if upstream improves the protocol. |
This may be more of a discussion-starter PR than something anyone really wants to land, but I've been interested in these conversions for a while :) I think there should eventually be a
ReadOnlyCell
type in the standard library, but it's an interesting quirk of the ecosystem that the only(?) project that's had a use for it so far is PyO3.Is there any practical use for these conversions? I'm not sure :) But they're spiritually similar to the
Cell::from_mut
conversion in the standard library. This makes it possible to write a function taking&ReadOnlyCell<T>
if you only need to copy theT
and you don't need the guarantee that it won't change from one read to the next (which it could, if other&Cell<T>
references exist pointing to the same value).With these conversions in place, the relationships between the different reference types look like this:
This sort of thing wasn't supported by Miri until recently, but the new Tree Borrows model supports it. The new test currently fails Miri by default:
But it passes with
-Zmiri-tree-borrows
: