Skip to content

Commit b1ca3aa

Browse files
authored
BoundedBuf trait to take .slice API out of IoBuf (#172)
Introduce new traits, `BoundedBuf` and `BoundedBufMut`, taking the `slice` method and related infrastructure off `IoBuf`. `Slice` no longer implements `IoBuf`/`IoBufMut`, instead it does `BoundedBuf` and `BoundedBufMut`. These traits are used as bounds in most generic I/O methods in place of `IoBuf`/`IoBufMut`. The purpose of this is to get rid of `Slice<Slice<T>>` arising from chained slice calls. * Any buffer the user has can be defined to be an IoBuf (subject to the safety constraints of IoBuf) * An IoBuf is by definition a BoundedBuf * BoundedBuf<IoBuf>.slice(range) -> Slice<IoBuf> (i.e. slicing an IoBuf BoundedBuf produces an IoBuf Slice) * A Slice<IoBuf> is also a BoundedBuf by definition * And to void the problem of a Slice<Slice<>> hierarchy building up * A BoundedBuf<Slice<IoBuf>>.slice(range) -> Slice<IoBuf> (keeping the slice flat) All calls that take a BoundedBuf input type can take an IoBuf and a Slice<IoBuf>. And a brief explanation for the few functions that were split into two layers, a generic public layer and a slice specific private layer: The purpose of factoring out the bulk of the public method's body into the private method working over Slice is twofold: * Unify the code path recovering the buffer passed in for the BufResult, rather than have it replicated in multiple return expressions as in #163; * Reduce monomorphization bloat, so that most of the machine code to work with buffers passed in as, say, Vec<u8> and Slice<Vec<u8>> is emitted in one instance, rather than replicated instances of exactly the same code.
1 parent f99f214 commit b1ca3aa

File tree

18 files changed

+451
-301
lines changed

18 files changed

+451
-301
lines changed

examples/tcp_listener.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ fn main() {
2222
tokio_uring::spawn(async move {
2323
// implement ping-pong loop
2424

25-
use tokio_uring::buf::IoBuf; // for slice()
25+
use tokio_uring::buf::BoundedBuf; // for slice()
2626

2727
println!("{} connected", socket_addr);
2828
let mut n = 0;

src/buf/bounded.rs

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
use super::{IoBuf, IoBufMut, Slice};
2+
3+
use std::ops;
4+
5+
/// A possibly bounded view into an owned [`IoBuf`] buffer.
6+
///
7+
/// Because buffers are passed by ownership to the runtime, Rust's slice API
8+
/// (`&buf[..]`) cannot be used. Instead, `tokio-uring` provides an owned slice
9+
/// API: [`.slice()`]. The method takes ownership of the buffer and returns a
10+
/// [`Slice`] value that tracks the requested range.
11+
///
12+
/// This trait provides a generic way to use buffers and `Slice` views
13+
/// into such buffers with `io-uring` operations.
14+
///
15+
/// [`.slice()`]: BoundedBuf::slice
16+
pub trait BoundedBuf: Unpin + 'static {
17+
/// The type of the underlying buffer.
18+
type Buf: IoBuf;
19+
20+
/// The type representing the range bounds of the view.
21+
type Bounds: ops::RangeBounds<usize>;
22+
23+
/// Returns a view of the buffer with the specified range.
24+
///
25+
/// This method is similar to Rust's slicing (`&buf[..]`), but takes
26+
/// ownership of the buffer. The range bounds are specified against
27+
/// the possibly offset beginning of the `self` view into the buffer
28+
/// and the end bound, if specified, must not exceed the view's total size.
29+
/// Note that the range may extend into the uninitialized part of the
30+
/// buffer, but it must start (if so bounded) in the initialized part
31+
/// or immediately adjacent to it.
32+
///
33+
/// # Panics
34+
///
35+
/// If the range is invalid with regard to the recipient's total size or
36+
/// the length of its initialized part, the implementation of this method
37+
/// should panic.
38+
///
39+
/// # Examples
40+
///
41+
/// ```
42+
/// use tokio_uring::buf::BoundedBuf;
43+
///
44+
/// let buf = b"hello world".to_vec();
45+
/// let slice = buf.slice(5..10);
46+
/// assert_eq!(&slice[..], b" worl");
47+
/// let slice = slice.slice(1..3);
48+
/// assert_eq!(&slice[..], b"wo");
49+
/// ```
50+
fn slice(self, range: impl ops::RangeBounds<usize>) -> Slice<Self::Buf>;
51+
52+
/// Returns a `Slice` with the view's full range.
53+
///
54+
/// This method is to be used by the `tokio-uring` runtime and it is not
55+
/// expected for users to call it directly.
56+
fn slice_full(self) -> Slice<Self::Buf>;
57+
58+
/// Gets a reference to the underlying buffer.
59+
fn get_buf(&self) -> &Self::Buf;
60+
61+
/// Returns the range bounds for this view.
62+
fn bounds(&self) -> Self::Bounds;
63+
64+
/// Constructs a view from an underlying buffer and range bounds.
65+
fn from_buf_bounds(buf: Self::Buf, bounds: Self::Bounds) -> Self;
66+
67+
/// Like [`IoBuf::stable_ptr`],
68+
/// but possibly offset to the view's starting position.
69+
fn stable_ptr(&self) -> *const u8;
70+
71+
/// Number of initialized bytes available via this view.
72+
fn bytes_init(&self) -> usize;
73+
74+
/// Total size of the view, including uninitialized memory, if any.
75+
fn bytes_total(&self) -> usize;
76+
}
77+
78+
impl<T: IoBuf> BoundedBuf for T {
79+
type Buf = Self;
80+
type Bounds = ops::RangeFull;
81+
82+
fn slice(self, range: impl ops::RangeBounds<usize>) -> Slice<Self> {
83+
use ops::Bound;
84+
85+
let begin = match range.start_bound() {
86+
Bound::Included(&n) => n,
87+
Bound::Excluded(&n) => n.checked_add(1).expect("out of range"),
88+
Bound::Unbounded => 0,
89+
};
90+
91+
assert!(begin < self.bytes_total());
92+
93+
let end = match range.end_bound() {
94+
Bound::Included(&n) => n.checked_add(1).expect("out of range"),
95+
Bound::Excluded(&n) => n,
96+
Bound::Unbounded => self.bytes_total(),
97+
};
98+
99+
assert!(end <= self.bytes_total());
100+
assert!(begin <= self.bytes_init());
101+
102+
Slice::new(self, begin, end)
103+
}
104+
105+
fn slice_full(self) -> Slice<Self> {
106+
let end = self.bytes_total();
107+
Slice::new(self, 0, end)
108+
}
109+
110+
fn get_buf(&self) -> &Self {
111+
self
112+
}
113+
114+
fn bounds(&self) -> Self::Bounds {
115+
..
116+
}
117+
118+
fn from_buf_bounds(buf: Self, _: ops::RangeFull) -> Self {
119+
buf
120+
}
121+
122+
fn stable_ptr(&self) -> *const u8 {
123+
IoBuf::stable_ptr(self)
124+
}
125+
126+
fn bytes_init(&self) -> usize {
127+
IoBuf::bytes_init(self)
128+
}
129+
130+
fn bytes_total(&self) -> usize {
131+
IoBuf::bytes_total(self)
132+
}
133+
}
134+
135+
/// A possibly bounded view into an owned [`IoBufMut`] buffer.
136+
///
137+
/// This trait provides a generic way to use mutable buffers and `Slice` views
138+
/// into such buffers with `io-uring` operations.
139+
pub trait BoundedBufMut: BoundedBuf<Buf = Self::BufMut> {
140+
/// The type of the underlying buffer.
141+
type BufMut: IoBufMut;
142+
143+
/// Like [`IoBufMut::stable_mut_ptr`],
144+
/// but possibly offset to the view's starting position.
145+
fn stable_mut_ptr(&mut self) -> *mut u8;
146+
147+
/// Like [`IoBufMut::set_init`],
148+
/// but the position is possibly offset to the view's starting position.
149+
///
150+
/// # Safety
151+
///
152+
/// The caller must ensure that all bytes starting at `stable_mut_ptr()` up
153+
/// to `pos` are initialized and owned by the buffer.
154+
unsafe fn set_init(&mut self, pos: usize);
155+
}
156+
157+
impl<T: IoBufMut> BoundedBufMut for T {
158+
type BufMut = T;
159+
160+
fn stable_mut_ptr(&mut self) -> *mut u8 {
161+
IoBufMut::stable_mut_ptr(self)
162+
}
163+
164+
unsafe fn set_init(&mut self, pos: usize) {
165+
IoBufMut::set_init(self, pos)
166+
}
167+
}

src/buf/io_buf.rs

Lines changed: 4 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,16 @@
1-
use crate::buf::Slice;
2-
3-
use std::ops;
4-
51
/// An `io-uring` compatible buffer.
62
///
7-
/// The `IoBuf` trait is implemented by buffer types that can be passed to
8-
/// io-uring operations. Users will not need to use this trait directly, except
9-
/// for the [`slice`] method.
10-
///
11-
/// # Slicing
12-
///
13-
/// Because buffers are passed by ownership to the runtime, Rust's slice API
14-
/// (`&buf[..]`) cannot be used. Instead, `tokio-uring` provides an owned slice
15-
/// API: [`slice()`]. The method takes ownership fo the buffer and returns a
16-
/// `Slice<Self>` type that tracks the requested offset.
3+
/// The `IoBuf` trait is implemented by buffer types that can be used with
4+
/// io-uring operations. Users will not need to use this trait directly.
5+
/// The [`BoundedBuf`] trait provides some useful methods including `slice`.
176
///
187
/// # Safety
198
///
209
/// Buffers passed to `io-uring` operations must reference a stable memory
2110
/// region. While the runtime holds ownership to a buffer, the pointer returned
2211
/// by `stable_ptr` must remain valid even if the `IoBuf` value is moved.
2312
///
24-
/// [`slice()`]: IoBuf::slice
13+
/// [`BoundedBuf`]: crate::buf::BoundedBuf
2514
pub unsafe trait IoBuf: Unpin + 'static {
2615
/// Returns a raw pointer to the vector’s buffer.
2716
///
@@ -48,45 +37,6 @@ pub unsafe trait IoBuf: Unpin + 'static {
4837
///
4938
/// For `Vec`, this is identical to `capacity()`.
5039
fn bytes_total(&self) -> usize;
51-
52-
/// Returns a view of the buffer with the specified range.
53-
///
54-
/// This method is similar to Rust's slicing (`&buf[..]`), but takes
55-
/// ownership of the buffer.
56-
///
57-
/// # Examples
58-
///
59-
/// ```
60-
/// use tokio_uring::buf::IoBuf;
61-
///
62-
/// let buf = b"hello world".to_vec();
63-
/// buf.slice(5..10);
64-
/// ```
65-
fn slice(self, range: impl ops::RangeBounds<usize>) -> Slice<Self>
66-
where
67-
Self: Sized,
68-
{
69-
use core::ops::Bound;
70-
71-
let begin = match range.start_bound() {
72-
Bound::Included(&n) => n,
73-
Bound::Excluded(&n) => n + 1,
74-
Bound::Unbounded => 0,
75-
};
76-
77-
assert!(begin < self.bytes_total());
78-
79-
let end = match range.end_bound() {
80-
Bound::Included(&n) => n.checked_add(1).expect("out of range"),
81-
Bound::Excluded(&n) => n,
82-
Bound::Unbounded => self.bytes_total(),
83-
};
84-
85-
assert!(end <= self.bytes_total());
86-
assert!(begin <= self.bytes_init());
87-
88-
Slice::new(self, begin, end)
89-
}
9040
}
9141

9242
unsafe impl IoBuf for Vec<u8> {

src/buf/io_buf_mut.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::buf::IoBuf;
22

33
/// A mutable`io-uring` compatible buffer.
44
///
5-
/// The `IoBufMut` trait is implemented by buffer types that can be passed to
5+
/// The `IoBufMut` trait is implemented by buffer types that can be used with
66
/// io-uring operations. Users will not need to use this trait directly.
77
///
88
/// # Safety
@@ -23,7 +23,8 @@ pub unsafe trait IoBufMut: IoBuf {
2323

2424
/// Updates the number of initialized bytes.
2525
///
26-
/// The specified `pos` becomes the new value returned by
26+
/// If the specified `pos` is greater than the value returned by
27+
/// [`IoBuf::bytes_init`], it becomes the new water mark as returned by
2728
/// `IoBuf::bytes_init`.
2829
///
2930
/// # Safety

src/buf/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ pub use io_buf_mut::IoBufMut;
1313
mod slice;
1414
pub use slice::Slice;
1515

16+
mod bounded;
17+
pub use bounded::{BoundedBuf, BoundedBufMut};
18+
1619
pub(crate) fn deref(buf: &impl IoBuf) -> &[u8] {
1720
// Safety: the `IoBuf` trait is marked as unsafe and is expected to be
1821
// implemented correctly.

0 commit comments

Comments
 (0)