Repository: tokio-rs/bytes (Rust)

Issue:
Preserve addresses of empty bytes in `slice`
I would like to use `Bytes` to create parsers for binary formats. To report precise error messages, I would like to rely on `Bytes::as_ptr() as usize` to get the current position in the main file. That works quite well ... except for when I run out of input. Consider the following example:

~~~ rust
    let b = Bytes::from_owner(b"Hi");
    for i in 0..=b.len() {
        let s = b.slice(i..);
        let offset = dbg!(dbg!(s.as_ptr() as usize) - dbg!(b.as_ptr() as usize));
    }
~~~

Here, the output is:

~~~
[src/main.rs:16:27] s.as_ptr() as usize = 101899852842091
[src/main.rs:16:55] b.as_ptr() as usize = 101899852842091
[src/main.rs:16:22] dbg!(s.as_ptr() as usize) - dbg!(b.as_ptr() as usize) = 0
[src/main.rs:16:27] s.as_ptr() as usize = 101899852842092
[src/main.rs:16:55] b.as_ptr() as usize = 101899852842091
[src/main.rs:16:22] dbg!(s.as_ptr() as usize) - dbg!(b.as_ptr() as usize) = 1
[src/main.rs:16:27] s.as_ptr() as usize = 1             <---------------------------------- !
[src/main.rs:16:55] b.as_ptr() as usize = 101899852842091
thread 'main' panicked at src/main.rs:16:27:
attempt to subtract with overflow
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
~~~

We can see that an empty `Bytes` loses its offset. This is caused by the [following code in `Bytes::slice`](https://github.com/tokio-rs/bytes/blob/141cbc22cbd04c4f3cf3d2af0a0f449322918d69/src/bytes.rs#L403C19-L405C10):

~~~ rust
        if end == begin {
            return Bytes::new();
        }
~~~

This code also contradicts the current documentation for `Bytes::slice`, which states:

> This will increment the reference count for the underlying memory and return a new Bytes handle set to the slice.

I would like to propose the removal of the `end == begin` check. Why?
Without this check in `Bytes::slice`, a user of `Bytes` can get the effect of this check by just doing the check manually before running `Bytes::slice`. However, with this check in `Bytes::slice`, it is impossible to disable it. Also, without this check, the behaviour of the function would automatically correspond to its documentation, which is currently not the case.


Discussion:
We already did something similar in #740, so this seems okay to add.
@Darksonn, thanks for your response! I will then make a PR in the spirit of #740.



Patch:
diff --git a/src/bytes.rs b/src/bytes.rs
index e2c187cc1..5c2ca18ad 100644
--- a/src/bytes.rs
+++ b/src/bytes.rs
@@ -401,7 +401,7 @@ impl Bytes {
         );
 
         if end == begin {
-            return Bytes::new();
+            return Bytes::new_empty_with_ptr(self.ptr.wrapping_add(begin));
         }
 
         let mut ret = self.clone();


Test patch:
diff --git a/tests/test_bytes.rs b/tests/test_bytes.rs
index 613efc886..7a02fb206 100644
--- a/tests/test_bytes.rs
+++ b/tests/test_bytes.rs
@@ -1405,6 +1405,26 @@ fn try_reclaim_arc() {
     assert_eq!(true, buf.try_reclaim(6));
 }
 
+#[test]
+fn slice_empty_addr() {
+    let buf = Bytes::from(vec![0; 1024]);
+
+    let ptr_start = buf.as_ptr();
+    let ptr_end = ptr_start.wrapping_add(1024);
+
+    let empty_end = buf.slice(1024..);
+    assert_eq!(empty_end.len(), 0);
+    assert_eq!(empty_end.as_ptr(), ptr_end);
+
+    let empty_start = buf.slice(..0);
+    assert_eq!(empty_start.len(), 0);
+    assert_eq!(empty_start.as_ptr(), ptr_start);
+
+    // Is miri happy about the provenance?
+    let _ = &empty_end[..];
+    let _ = &empty_start[..];
+}
+
 #[test]
 fn split_off_empty_addr() {
     let mut buf = Bytes::from(vec![0; 1024]);