Skip to content

Commit 17f36e9

Browse files
[APMSP-1583] Fix hash in BytesString (#767)
* Fix hash implementation * Use Self as in impl * Add From impl * Add tests for hash and from trait
1 parent d339c64 commit 17f36e9

File tree

1 file changed

+66
-14
lines changed

1 file changed

+66
-14
lines changed

tinybytes/src/bytes_string.rs

Lines changed: 66 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44
use crate::Bytes;
55
#[cfg(feature = "serde")]
66
use serde::ser::{Serialize, Serializer};
7-
use std::borrow::Borrow;
8-
use std::str::Utf8Error;
7+
use std::{borrow::Borrow, hash, str::Utf8Error};
98

10-
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
9+
#[derive(Clone, Debug, Eq, PartialEq)]
1110
pub struct BytesString {
1211
bytes: Bytes,
1312
}
@@ -40,9 +39,9 @@ impl BytesString {
4039
/// # Errors
4140
///
4241
/// Returns a `Utf8Error` if the bytes are not valid UTF-8.
43-
pub fn from_slice(slice: &[u8]) -> Result<BytesString, Utf8Error> {
42+
pub fn from_slice(slice: &[u8]) -> Result<Self, Utf8Error> {
4443
std::str::from_utf8(slice)?;
45-
Ok(BytesString {
44+
Ok(Self {
4645
bytes: Bytes::copy_from_slice(slice),
4746
})
4847
}
@@ -64,9 +63,9 @@ impl BytesString {
6463
/// # Errors
6564
///
6665
/// Returns a `Utf8Error` if the bytes are not valid UTF-8.
67-
pub fn from_bytes(bytes: Bytes) -> Result<BytesString, Utf8Error> {
66+
pub fn from_bytes(bytes: Bytes) -> Result<Self, Utf8Error> {
6867
std::str::from_utf8(&bytes)?;
69-
Ok(BytesString { bytes })
68+
Ok(Self { bytes })
7069
}
7170

7271
/// Creates a `BytesString` from a string slice within the given buffer.
@@ -75,12 +74,10 @@ impl BytesString {
7574
///
7675
/// * `bytes` - A `tinybytes::Bytes` instance that will be converted into a `BytesString`.
7776
/// * `slice` - The string slice pointing into the given bytes that will form the `BytesString`.
78-
pub fn from_bytes_slice(bytes: &Bytes, slice: &str) -> BytesString {
77+
pub fn from_bytes_slice(bytes: &Bytes, slice: &str) -> Self {
7978
// SAFETY: This is safe as a str slice is definitely a valid UTF-8 slice.
8079
unsafe {
81-
BytesString::from_bytes_unchecked(
82-
bytes.slice_ref(slice.as_bytes()).expect("Invalid slice"),
83-
)
80+
Self::from_bytes_unchecked(bytes.slice_ref(slice.as_bytes()).expect("Invalid slice"))
8481
}
8582
}
8683

@@ -97,8 +94,8 @@ impl BytesString {
9794
///
9895
/// This function is unsafe because it assumes the bytes are valid UTF-8. If the bytes are not
9996
/// valid UTF-8, the behavior is undefined.
100-
pub unsafe fn from_bytes_unchecked(bytes: Bytes) -> BytesString {
101-
BytesString { bytes }
97+
pub unsafe fn from_bytes_unchecked(bytes: Bytes) -> Self {
98+
Self { bytes }
10299
}
103100

104101
/// Returns the string slice representation of the `BytesString` (without validating the bytes).
@@ -113,7 +110,7 @@ impl BytesString {
113110

114111
impl Default for BytesString {
115112
fn default() -> Self {
116-
BytesString {
113+
Self {
117114
bytes: Bytes::empty(),
118115
}
119116
}
@@ -125,9 +122,38 @@ impl Borrow<str> for BytesString {
125122
}
126123
}
127124

125+
impl AsRef<str> for BytesString {
126+
fn as_ref(&self) -> &str {
127+
self.as_str()
128+
}
129+
}
130+
131+
impl From<String> for BytesString {
132+
fn from(value: String) -> Self {
133+
// SAFETY: This is safe as a String is always a valid UTF-8 slice.
134+
unsafe { Self::from_bytes_unchecked(Bytes::from_underlying(value)) }
135+
}
136+
}
137+
138+
impl From<&'static str> for BytesString {
139+
fn from(value: &'static str) -> Self {
140+
// SAFETY: This is safe as a str is always a valid UTF-8 slice.
141+
unsafe { Self::from_bytes_unchecked(Bytes::from_static(value.as_bytes())) }
142+
}
143+
}
144+
145+
// We can't derive Hash from Bytes as [u8] and str do not provide the same hash
146+
impl hash::Hash for BytesString {
147+
#[inline]
148+
fn hash<H: hash::Hasher>(&self, state: &mut H) {
149+
self.as_str().hash(state);
150+
}
151+
}
152+
128153
#[cfg(test)]
129154
mod tests {
130155
use super::*;
156+
use std::hash::{DefaultHasher, Hash, Hasher};
131157

132158
#[test]
133159
fn test_from_slice() {
@@ -190,4 +216,30 @@ mod tests {
190216
let borrowed: &str = bytes_string.borrow();
191217
assert_eq!(borrowed, "borrow");
192218
}
219+
220+
#[test]
221+
fn from_string() {
222+
let string = String::from("hello");
223+
let bytes_string = BytesString::from(string);
224+
assert_eq!(bytes_string.as_str(), "hello")
225+
}
226+
227+
#[test]
228+
fn from_static_str() {
229+
let static_str = "hello";
230+
let bytes_string = BytesString::from(static_str);
231+
assert_eq!(bytes_string.as_str(), "hello")
232+
}
233+
234+
fn calculate_hash<T: Hash>(t: &T) -> u64 {
235+
let mut s = DefaultHasher::new();
236+
t.hash(&mut s);
237+
s.finish()
238+
}
239+
240+
#[test]
241+
fn hash() {
242+
let bytes_string = BytesString::from_slice(b"test hash").unwrap();
243+
assert_eq!(calculate_hash(&bytes_string), calculate_hash(&"test hash"));
244+
}
193245
}

0 commit comments

Comments
 (0)