-
Notifications
You must be signed in to change notification settings - Fork 131
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
Improve the ergonomics of .slice with IntoSlice trait #53
Conversation
src/buf/slice.rs
Outdated
/// | ||
/// This method is to be used by the `tokio-uring` runtime and it is not | ||
/// expected for users to call it directly. | ||
pub fn stable_ptr(&self) -> *const u8 { |
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.
I'm not sure it's a good idea to combine inherent methods with the Deref
impl, as per the API guidelines C-DEREF, C-SMART-PTR.
As these methods are not meant to be called by the API user, perhaps they should be turned into associated functions and/or made private.
On the other hand, the Deref
/ DerefMut
impls for Slice
could be considered for removal in favor of explicit accessor methods like .as_bytes()
/ .as_mut_bytes()
. Especially if we'd want to equip Slice
with more non-slicey methods for appending to buffers, like .put_bytes
, .put_u32_le
, etc.
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.
explicit accessor methods like
.as_bytes()
/.as_mut_bytes()
... or a std::ops::Index
impl, as this is what the users would need the most from a buffer, and [..]
is a concise adapter syntax for anything else where buffer contents would be used as a byte slice.
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.
I have realized that the IoBuf
/IoBufMut
-cum-inherent-Slice
methods do not need to be public, so I've made them pub(crate)
. But the above comments still apply to the other inherent methods of Slice
.
@mzabaluev any interest in completing this? |
@Noah-Kennedy Yes, I should update the PR at least, will get to it soon. |
4717bf6
to
8b74f9e
Compare
@Noah-Kennedy I have rebased the branch against master and updated the newly added socket ops |
The module's code is no longer in use.
b"hello".to_vec().slice(1..).slice(..3) produces a Slice<Slice<Vec<u8>>>. The API makes it easy to inadvertendly end up with nested Slice wrappers, which adds unnecessary overhead to the buffer accessor methods and makes the results of .begin() and .end() difficult to interpret. Introduce trait IntoSlice to uniformly convert all buffer parameters to a Slice<T>, with the impl of InfoSlice for Slice<T> closing onto Self. The buffer parameter type in the generic I/O operation methods is bound with IntoSlice to keep the polymorphism good, while reducing code bloat in monomorphisation when the application passes both buffers and their slices to tokio-uring operations. Consequently, all buffers returned from the operations are wrapped into Slice.
Judging by the impls and the tests, it's clearly meant to work as a high water mark for initialized portion of the buffer, there is no setting the position back. The documentation on Slice::set_init has already been made correct, so it makes sense to fix this in the same PR.
The doc tests need update after the original IntoSlice refactoring.
8b74f9e
to
c357648
Compare
@mzabaluev I know you were asked this before. Any interest in merging with master and pushing up changes again? This looks like it was going to be important and @Noah-Kennedy was happy with the direction but was left requiring another review. I would like to help if I can. |
@Noah-Kennedy Should this go in? I can work on rebasing. |
I will try to update this over the weekend. |
ed7c0b3
to
55b7c1e
Compare
@mzabaluev will you have a look at these CI errors? I suspect they come from documentation examples that were added since you started this PR. If you don't have time, I can take a look today. |
@mzabaluev I have fixed up the CI issues locally. Let me know if its okay with you that I push them up to this branch. |
All, I'll add I'm also looking at this, although my aims are a little broader. I think |
@FrankReh Go ahead. I suppose the newly added methods like |
@mzabaluev Would you mind reviewing the changes I made to the write_all example and write_all method for tcp, and perhaps the doc examples that use write_all too? Please check that the signatures look right, to be conforming with the others you had changed, that my logic for making progress in the loop inside write_all looks right to you and perhaps also that the progress method seems idiomatic, and finally, my use of in_begin and in_end to track what the original slice looked like coming in so it could be returned. |
We expect the test to fail today for reasons not related to this commit. |
@FrankReh The |
@mzabaluev |
There's no need to expose raw buffer access methods of Slice in the public API.
You are right, I've now fixed that. |
Okay. Will wait on @Noah-Kennedy as he had something he wanted to get in first. This still looks good to me. |
pub trait IntoSlice: Sized { | ||
/// The buffer type underlying the produced slices. | ||
type Buf: IoBuf; | ||
|
||
/// Returns a view of the buffer with the specified range. | ||
/// The effective range bounds are computed against the potentially offset | ||
/// buffer view that this method is called on, which may itself be a slice. | ||
/// | ||
/// This method is similar to Rust's slicing (`&buf[..]`), but takes | ||
/// ownership of the buffer. | ||
/// | ||
/// # Examples | ||
/// | ||
/// ``` | ||
/// use tokio_uring::buf::IntoSlice; | ||
/// | ||
/// let buf = b"hello world".to_vec(); | ||
/// buf.slice(5..10); | ||
/// ``` | ||
fn slice(self, range: impl ops::RangeBounds<usize>) -> Slice<Self::Buf>; | ||
|
||
/// Converts the buffer into a [`Slice`] covering its full extent. | ||
/// | ||
/// This method is used internally by the `io-uring` operations and the | ||
/// end user does not need to call it directly. | ||
/// The implementation should be equivalent to `self.slice(..)`, but | ||
/// can be optimized by skipping range checks. | ||
fn into_full_slice(self) -> Slice<Self::Buf> { | ||
self.slice(..) | ||
} | ||
} |
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.
Could slice become a member function of impl Slice
, and this trait be replaced with an instance of From<T: IoBuf> for Slice
. I prefer that because in the absence of specialization, we can then repeatedly chain calls to slice, and they would then operate without creating nested slice instances.
let buf = vec![0u8;4096];
// Creates a slice over the full range
let mut my_slice: Slice<_> = buf.into();
// Slice over 32 bytes offset at 16, and also demonstrate that post slice, the indeces are into the visible region of the slice
my_slice.slice(16..48).slice(0..);
With this approach, we leave IoBuf alone, and the API functions still take T: IoBuf
, On return, if a slice is needed by the API (like for read) .into() will produce a Slice, and if we started with Slice, it'll be a no op rather than Slice<Slice>
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.
And also because slice is implments IoBuf, inputs to API calls don't need to be slice, but returned values on read-like function will be.
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.
I did start implmenting this, but got a bit carried away replacing IoBuf with Pin. I think that is a more involved change
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.
It's an interesting idea; the API methods always returning Slice<T>
is a bit of a bump when you actually need T
.
Would IoBuf
lose its slice
method then? If not, <Slice<T> as IoBuf>::slice
remains a wart that could still get called inadvertently in some generic code.
I've started exploring this and came up with #163. I did not expect type inference to work as well as it does there.
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.
<Slice<T> as IoBuf>::slice
remains a wart
I think this can be fixed by adding an associated type to IoBuf
. So I'm going to make yet another PR to explore that.
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.
I think this can be fixed by adding an associated type to
IoBuf
.
I could not make that work. However, I favour #163 over this PR now.
Superseded by #163. |
Add infrastructure to manage pre-registered buffers, and operation methods `File::read_fixed_at` and `File::write_fixed_at` to make use of them. Exclusive access to buffer data between the application and in-flight ops is controlled at runtime. This is initial API to enable fixed buffers. Future developments may include: - ✅ Improved parameter polymorphism with ~#53~ #172; - An internal linked list of free buffers, to be able to check out the next available buffer from `FixedBufRegistry` without enumerating or keeping track of indices; - Support `IORING_REGISTER_BUFFERS2`/`IORING_REGISTER_BUFFERS_UPDATE`.
The owned slice API has a flaw:
b"hello".to_vec().slice(1..).slice(..3)
produces aSlice<Slice<Vec<u8>>>
.It is easy to inadvertently end up with nested
Slice
wrappers, which adds unnecessary overhead to the buffer accessor methods and makes the results of.begin()
and.end()
difficult to interpret.Move the
slice
method to a new traitIntoSlice
, which is used to uniformly convert buffer parameters to aSlice<T>
, with the impl ofInfoSlice
forSlice<T>
closing ontoSlice<T>
.Instead of
IoBuf
which can no longer be implemented forSlice
, the buffer parameter type in the generic I/O operation methods is bound withIntoSlice
to keep the convenient polymorphism, while reducing code bloat in monomorphization when the application passes both buffers and their slices to tokio-uring operations. Consequently, all buffers returned from the operations are wrapped intoSlice
.This PR incorporates #52 in order to get tests to pass.