Skip to content

Commit bb3d54c

Browse files
authored
Bump inline-array functionality, fix serde bug and add other functionality (#1)
2 parents 8463b34 + fbf36f7 commit bb3d54c

File tree

6 files changed

+213
-36
lines changed

6 files changed

+213
-36
lines changed

.github/workflows/ci.yml

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ name: Rust Build
22

33
on:
44
push:
5-
branches: [ "main" ]
5+
branches: ["main"]
66
pull_request:
7-
branches: [ "main" ]
7+
branches: ["main"]
88

99
env:
1010
CARGO_TERM_COLOR: always
@@ -13,8 +13,15 @@ jobs:
1313
build:
1414
runs-on: ubuntu-latest
1515
steps:
16-
- uses: actions/checkout@v4
17-
- name: Build
18-
run: cargo build --verbose
19-
- name: Run tests
20-
run: cargo test --verbose
16+
- uses: actions/checkout@v4
17+
- uses: taiki-e/install-action@cargo-hack
18+
- name: Build
19+
run: cargo hack build --feature-powerset
20+
- name: Run tests
21+
run: cargo hack test --feature-powerset
22+
- name: Lint
23+
run: cargo hack clippy --all-targets --feature-powerset
24+
- name: Fmt
25+
run: cargo fmt --all --check
26+
- name: Build docs
27+
run: RUSTDOCFLAGS="-D warnings" cargo doc --no-deps

Cargo.toml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "inline-str"
3-
version = "0.4.0"
4-
edition = "2021"
3+
version = "0.5.0"
4+
edition = "2024"
55
authors = ["Adam Gutglick <[email protected]>"]
66
description = "Efficent and immutable string type, backed by inline-array"
77
license = "Apache-2.0 OR MIT"
@@ -12,8 +12,11 @@ keywords = ["string", "compact", "stack", "immutable", "database"]
1212
categories = ["data-structures", "compression"]
1313

1414
[dependencies]
15-
inline-array = "0.1.13"
16-
serde = { version = "1.0", features = ["derive"], optional = true }
15+
inline-array = "0.1.14"
16+
serde = { version = "1", features = ["derive"], optional = true }
17+
18+
[dev-dependencies]
19+
serde_json = "1"
1720

1821
[features]
1922
serde = ["inline-array/serde", "dep:serde"]

LICENSE-MIT

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright 2024 Adam Gutglick
1+
Copyright 2025 Adam Gutglick
22

33
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
44

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# inline-str
22

3-
`inline-str` is a small and cheaply cloned string type, intended for use in cases where you expect to be cloning the same short string many times.
3+
`inline-str` is a small and cheaply cloned string type, intended for use in cases where you expect to be cloning the same somewhat short string many times.
44

55
It is a thin layer over [`inline-array`](https://github.com/komora-io/inline-array) inspired by [@spacejam's](https://github.com/spacejam) work who suggested I build this crate a while back.
66

src/lib.rs

Lines changed: 154 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,82 @@
1-
// Copyright 2024 Adam Gutglick
2-
1+
// Copyright 2025 Adam Gutglick
2+
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
55
// You may obtain a copy of the License at
6-
6+
//
77
// http://www.apache.org/licenses/LICENSE-2.0
8-
8+
//
99
// Unless required by applicable law or agreed to in writing, software
1010
// distributed under the License is distributed on an "AS IS" BASIS,
1111
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
//! A string type that stores strings inline when small.
16+
//!
17+
//! `InlineStr` is a string type built on top of [`inline-array`] that can store small strings
18+
//! directly inline to avoid heap allocation, falling back to heap allocation for larger strings.
19+
//!
20+
//! This crate doesn't do any of the heavy lifting, if you want to better understand how it works
21+
//! its recommended to read through inline-array's docs and source code.
22+
//!
23+
//! # Examples
24+
//!
25+
//! ```
26+
//! use inline_str::InlineStr;
27+
//!
28+
//! let s = InlineStr::from("hello");
29+
//! assert_eq!(s, "hello");
30+
//! ```
31+
//!
32+
//! # Features
33+
//!
34+
//! - **serde**: Enable serialization/deserialization support with serde
35+
//!
36+
//! [`inline-array`]: https://crates.io/crates/inline-array
37+
38+
#![deny(clippy::doc_markdown)]
39+
#![deny(missing_docs)]
40+
1541
use core::str;
16-
use std::{borrow::Cow, ops::Deref};
42+
use std::{
43+
borrow::{Borrow, Cow},
44+
cmp::Ordering,
45+
ffi::OsStr,
46+
ops::Deref,
47+
path::Path,
48+
};
49+
50+
#[cfg(feature = "serde")]
51+
mod serde;
1752

1853
use inline_array::InlineArray;
1954

20-
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)]
21-
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
55+
/// Immutable stack-inlinable string type that can be cheaply cloned and shared.
56+
#[derive(PartialEq, Eq, Clone)]
2257
pub struct InlineStr {
2358
inner: InlineArray,
2459
}
2560

61+
impl InlineStr {
62+
/// Extracts a string slice containing the entire `InlineStr`.
63+
pub fn as_str(&self) -> &str {
64+
// Safety:
65+
// InlineStr can only be created from valid UTF8 byte sequences
66+
unsafe { str::from_utf8_unchecked(&self.inner) }
67+
}
68+
69+
/// Returns the length of the `InlineStr` in **bytes**.
70+
pub fn len(&self) -> usize {
71+
self.inner.len()
72+
}
73+
74+
/// Returns `true` if this `InlineStr` has a length of 0 (in bytes), otherwise `false`.
75+
pub fn is_empty(&self) -> bool {
76+
self.len() == 0
77+
}
78+
}
79+
2680
impl std::fmt::Display for InlineStr {
2781
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2882
std::fmt::Display::fmt(&**self, f)
@@ -37,7 +91,7 @@ impl std::fmt::Debug for InlineStr {
3791

3892
impl std::hash::Hash for InlineStr {
3993
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
40-
let as_str: &str = &*self;
94+
let as_str = &**self;
4195
as_str.hash(state);
4296
}
4397
}
@@ -66,55 +120,109 @@ impl From<&str> for InlineStr {
66120
}
67121
}
68122

69-
impl Deref for InlineStr {
70-
type Target = str;
123+
impl PartialOrd for InlineStr {
124+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
125+
Some(self.cmp(other))
126+
}
127+
}
71128

72-
fn deref(&self) -> &Self::Target {
73-
// Safety:
74-
// InlineStr can only be created from valid UTF8 byte sequences
75-
unsafe { str::from_utf8_unchecked(&self.inner) }
129+
impl Ord for InlineStr {
130+
fn cmp(&self, other: &Self) -> Ordering {
131+
self.as_str().cmp(other.as_str())
76132
}
77133
}
78134

79135
impl PartialEq<String> for InlineStr {
80136
fn eq(&self, other: &String) -> bool {
81-
(**self).eq(other)
137+
self.as_str() == other
82138
}
83139
}
84140

85141
impl PartialEq<InlineStr> for String {
86142
fn eq(&self, other: &InlineStr) -> bool {
87-
other.eq(self)
143+
self.as_str() == other.as_str()
88144
}
89145
}
90146

91-
impl<'a> PartialEq<&'a str> for InlineStr {
92-
fn eq(&self, other: &&'a str) -> bool {
93-
(&&**self).eq(other)
147+
impl PartialEq<&'_ str> for InlineStr {
148+
fn eq(&self, other: &&str) -> bool {
149+
self.as_str() == *other
94150
}
95151
}
96152

97153
impl PartialEq<InlineStr> for &str {
98154
fn eq(&self, other: &InlineStr) -> bool {
99-
other.eq(self)
155+
*self == other.as_str()
100156
}
101157
}
102158

159+
impl PartialEq<&InlineStr> for &str {
160+
fn eq(&self, other: &&InlineStr) -> bool {
161+
self == *other
162+
}
163+
}
103164
impl PartialEq<Cow<'_, str>> for InlineStr {
104165
fn eq(&self, other: &Cow<'_, str>) -> bool {
105-
(**self).eq(other)
166+
self.as_str() == other
106167
}
107168
}
108169

109170
impl PartialEq<InlineStr> for Cow<'_, str> {
110171
fn eq(&self, other: &InlineStr) -> bool {
111-
other.eq(self)
172+
self.as_ref() == other.as_str()
173+
}
174+
}
175+
176+
impl PartialEq<InlineStr> for &InlineStr {
177+
fn eq(&self, other: &InlineStr) -> bool {
178+
self.as_str() == other.as_str()
179+
}
180+
}
181+
182+
impl Deref for InlineStr {
183+
type Target = str;
184+
185+
fn deref(&self) -> &Self::Target {
186+
self.as_str()
187+
}
188+
}
189+
190+
impl AsRef<str> for InlineStr {
191+
fn as_ref(&self) -> &str {
192+
self
193+
}
194+
}
195+
196+
impl AsRef<Path> for InlineStr {
197+
fn as_ref(&self) -> &Path {
198+
self.as_str().as_ref()
199+
}
200+
}
201+
202+
impl AsRef<[u8]> for InlineStr {
203+
fn as_ref(&self) -> &[u8] {
204+
self.inner.as_ref()
205+
}
206+
}
207+
208+
impl AsRef<OsStr> for InlineStr {
209+
fn as_ref(&self) -> &OsStr {
210+
self.as_str().as_ref()
211+
}
212+
}
213+
214+
impl Borrow<str> for InlineStr {
215+
fn borrow(&self) -> &str {
216+
self.as_ref()
112217
}
113218
}
114219

115220
#[cfg(test)]
116221
mod tests {
117-
use std::hash::{BuildHasher, RandomState};
222+
use std::{
223+
collections::HashMap,
224+
hash::{BuildHasher, RandomState},
225+
};
118226

119227
use super::*;
120228

@@ -142,4 +250,27 @@ mod tests {
142250
assert_eq!(words_hash, words_hash_2);
143251
assert_eq!(words_hash, inline_hash);
144252
}
253+
254+
#[test]
255+
fn test_borrow() {
256+
let map = [(InlineStr::from("x"), 5)]
257+
.into_iter()
258+
.collect::<HashMap<InlineStr, i32>>();
259+
260+
let v = map.get("x");
261+
assert_eq!(v, Some(&5));
262+
}
263+
264+
#[cfg(feature = "serde")]
265+
#[test]
266+
fn test_serde() {
267+
let s = "hello world";
268+
let inline_s = InlineStr::from("hello world");
269+
assert_eq!(s, inline_s);
270+
let serialized_s = serde_json::to_value(s).unwrap();
271+
let serialized_inline = serde_json::to_value(inline_s.as_str()).unwrap();
272+
assert_eq!(serialized_s, serialized_inline);
273+
let deserialized: InlineStr = serde_json::from_value(serialized_s).unwrap();
274+
assert_eq!(deserialized, "hello world");
275+
}
145276
}

src/serde.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright 2025 Adam Gutglick
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use serde::{Deserialize, Serialize, de::Deserializer, ser::Serializer};
16+
17+
use crate::InlineStr;
18+
19+
impl Serialize for InlineStr {
20+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
21+
where
22+
S: Serializer,
23+
{
24+
serializer.serialize_str(self.as_str())
25+
}
26+
}
27+
28+
impl<'de> Deserialize<'de> for InlineStr {
29+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
30+
where
31+
D: Deserializer<'de>,
32+
{
33+
let s = String::deserialize(deserializer)?;
34+
Ok(InlineStr::from(s))
35+
}
36+
}

0 commit comments

Comments
 (0)