gst: implement IdStr bindings and compatibility versions

IdStr represents UTF-8 immutable strings which perform optimizations for short
strings (< 16 bytes). The C type `GstIdStr` was introduced in GStreamer 1.26 as
a replacement for GQuarks.

This commit adds Rust bindings for the C type `GstIdStr`. Since this type will
be used in API which previously relied on GQuarks, the commit also adds a
compatibility implementation which can be used with GStreamer versions prior to
1.26, which is the first version to implement and use `GstIdStr`.

The crate [KString] was used as the inner implementation for the compatibility
version as it performs similar optimizations as `GstIdStr` and uses the same
threshold to trigger heap allocation.

See also: https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/7432

[KString]: https://crates.io/crates/kstring

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1531>
This commit is contained in:
François Laignel 2024-09-30 21:20:02 +02:00
parent bad44ef436
commit c2cc048803
7 changed files with 1587 additions and 0 deletions

16
Cargo.lock generated
View file

@ -884,6 +884,7 @@ dependencies = [
"glib", "glib",
"gstreamer-sys", "gstreamer-sys",
"itertools", "itertools",
"kstring",
"libc", "libc",
"log", "log",
"muldiv", "muldiv",
@ -1705,6 +1706,15 @@ version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
[[package]]
name = "kstring"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "558bf9508a558512042d3095138b1f7b8fe90c5467d94f9f1da28b3731c5dbd1"
dependencies = [
"static_assertions",
]
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.161" version = "0.2.161"
@ -2293,6 +2303,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.87" version = "2.0.87"

View file

@ -23,6 +23,7 @@ num-rational = { version = "0.4", default-features = false, features = [] }
futures-core = "0.3" futures-core = "0.3"
futures-channel = "0.3" futures-channel = "0.3"
futures-util = { version = "0.3", default-features = false } futures-util = { version = "0.3", default-features = false }
kstring = "2.0"
log = { version = "0.4", optional = true } log = { version = "0.4", optional = true }
muldiv = "1" muldiv = "1"
opt-ops = { package = "option-operations", version = "0.5" } opt-ops = { package = "option-operations", version = "0.5" }

View file

@ -0,0 +1,551 @@
// Take a look at the license at the top of the repository in the LICENSE file.
// rustdoc-stripper-ignore-next
//! The `IdStr` bindings of the C type `GstIdStr`.
//!
//! See the higher level module documentation for details.
use crate::ffi;
use glib::{translate::*, GStr, GString};
use std::{
cmp,
ffi::{c_char, CStr},
fmt,
hash::{Hash, Hasher},
mem,
ops::Deref,
ptr::NonNull,
};
glib::wrapper! {
// rustdoc-stripper-ignore-next
/// An UTF-8 immutable string type with optimizations for short values (len < 16).
#[derive(Debug)]
#[doc(alias = "GstIdStr")]
pub struct IdStr(BoxedInline<ffi::GstIdStr>);
match fn {
copy => |ptr| ffi::gst_id_str_copy(ptr),
free => |ptr| ffi::gst_id_str_free(ptr),
init => |ptr| ffi::gst_id_str_init(ptr),
copy_into => |dest, src| ffi::gst_id_str_copy_into(dest, src),
clear => |ptr| ffi::gst_id_str_clear(ptr),
}
}
impl IdStr {
#[doc(alias = "gst_id_str_new")]
#[inline]
pub const fn new() -> IdStr {
skip_assert_initialized!();
unsafe {
// Safety: empty inlined string consists in the type being all zeroed
IdStr {
inner: mem::zeroed(),
}
}
}
// rustdoc-stripper-ignore-next
/// Builds an `IdStr` from the given static `GStr`.
///
/// This constructor performs optimizations which other constructors can't rely on.
///
/// To build an `IdStr` from a string literal, use the [`idstr`](crate::idstr) macro.
#[inline]
pub fn from_static<T: AsRef<GStr> + ?Sized>(value: &'static T) -> IdStr {
skip_assert_initialized!();
let mut ret = IdStr::new();
ret.set_static(value);
ret
}
#[doc(alias = "gst_id_str_new")]
#[inline]
pub fn from<T: AsRef<str>>(value: T) -> IdStr {
skip_assert_initialized!();
let mut id = IdStr::new();
id.set(value);
id
}
#[doc(alias = "gst_id_str_get_len")]
#[inline]
pub fn len(&self) -> usize {
unsafe { ffi::gst_id_str_get_len(self.to_glib_none().0) }
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
// rustdoc-stripper-ignore-next
/// Returns the pointer to the nul terminated string `value` represented by this `IdStr`.
#[inline]
fn as_char_ptr(&self) -> NonNull<c_char> {
unsafe {
let ptr = ffi::gst_id_str_as_str(self.to_glib_none().0);
debug_assert!(!ptr.is_null());
let nn = NonNull::<c_char>::new_unchecked(ptr as *mut _);
debug_assert_eq!(*nn.as_ptr().add(self.len()), 0, "expecting nul terminator");
nn
}
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
unsafe {
// Safety: `as_char_ptr()` returns a non-null pointer to a nul terminated string.
std::slice::from_raw_parts(self.as_char_ptr().as_ptr() as *const _, self.len())
}
}
#[inline]
fn as_bytes_with_nul(&self) -> &[u8] {
unsafe {
// Safety: `as_char_ptr()` returns a non-null pointer to a nul terminated string.
std::slice::from_raw_parts(self.as_char_ptr().as_ptr() as *const _, self.len() + 1)
}
}
#[inline]
pub fn as_str(&self) -> &str {
cfg_if::cfg_if! {
if #[cfg(debug_assertions)] {
std::str::from_utf8(self.as_bytes()).unwrap()
} else {
unsafe {
std::str::from_utf8_unchecked(self.as_bytes())
}
}
}
}
#[doc(alias = "gst_id_str_as_str")]
#[inline]
pub fn as_gstr(&self) -> &GStr {
cfg_if::cfg_if! {
if #[cfg(debug_assertions)] {
GStr::from_utf8_with_nul(self.as_bytes_with_nul()).unwrap()
} else {
unsafe {
GStr::from_utf8_with_nul_unchecked(self.as_bytes_with_nul())
}
}
}
}
#[inline]
pub fn as_cstr(&self) -> &CStr {
cfg_if::cfg_if! {
if #[cfg(debug_assertions)] {
CStr::from_bytes_with_nul(self.as_bytes_with_nul()).unwrap()
} else {
unsafe {
CStr::from_bytes_with_nul_unchecked(self.as_bytes_with_nul())
}
}
}
}
#[doc(alias = "gst_id_str_is_equal")]
#[inline]
fn is_equal(&self, s2: &IdStr) -> bool {
unsafe {
from_glib(ffi::gst_id_str_is_equal(
self.to_glib_none().0,
s2.to_glib_none().0,
))
}
}
#[doc(alias = "gst_id_str_is_equal_to_str_with_len")]
#[inline]
fn is_equal_to_str(&self, s2: impl AsRef<str>) -> bool {
unsafe {
let s2 = s2.as_ref();
from_glib(ffi::gst_id_str_is_equal_to_str_with_len(
self.to_glib_none().0,
s2.as_ptr() as *const c_char,
s2.len(),
))
}
}
// rustdoc-stripper-ignore-next
/// Sets `self` to the static string `value`.
///
/// This function performs optimizations which [IdStr::set] can't rely on.
///
/// To build an `IdStr` from a string literal, use the [`idstr`](crate::idstr) macro.
#[doc(alias = "gst_id_str_set_static_str")]
#[doc(alias = "gst_id_str_set_static_str_with_len")]
#[inline]
pub fn set_static<T: AsRef<GStr> + ?Sized>(&mut self, value: &'static T) {
unsafe {
let v = value.as_ref();
ffi::gst_id_str_set_static_str_with_len(
self.to_glib_none_mut().0,
v.to_glib_none().0,
v.len(),
);
}
}
// rustdoc-stripper-ignore-next
/// Sets `self` to the string `value`.
///
/// For a static value, use [IdStr::set_static] which can perform optimizations.
///
/// To build an `IdStr` from a string literal, use the [`idstr`](crate::idstr) macro.
#[doc(alias = "gst_id_str_set")]
#[doc(alias = "gst_id_str_set_with_len")]
#[inline]
pub fn set(&mut self, value: impl AsRef<str>) {
unsafe {
let v = value.as_ref();
ffi::gst_id_str_set_with_len(
self.to_glib_none_mut().0,
v.as_ptr() as *const c_char,
v.len(),
);
}
}
}
impl Default for IdStr {
fn default() -> Self {
Self::new()
}
}
impl Deref for IdStr {
type Target = GStr;
fn deref(&self) -> &Self::Target {
self.as_gstr()
}
}
impl AsRef<str> for IdStr {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsRef<IdStr> for IdStr {
#[inline]
fn as_ref(&self) -> &IdStr {
self
}
}
impl AsRef<GStr> for IdStr {
#[inline]
fn as_ref(&self) -> &GStr {
self.as_gstr()
}
}
impl AsRef<CStr> for IdStr {
#[inline]
fn as_ref(&self) -> &CStr {
self.as_cstr()
}
}
impl From<&str> for IdStr {
#[inline]
fn from(value: &str) -> IdStr {
skip_assert_initialized!();
let mut ret = IdStr::new();
ret.set(value);
ret
}
}
impl From<&String> for IdStr {
#[inline]
fn from(value: &String) -> IdStr {
skip_assert_initialized!();
let mut ret = IdStr::new();
ret.set(value);
ret
}
}
impl From<String> for IdStr {
#[inline]
fn from(value: String) -> IdStr {
skip_assert_initialized!();
let mut ret = IdStr::new();
ret.set(&value);
ret
}
}
impl From<&GStr> for IdStr {
#[inline]
fn from(value: &GStr) -> IdStr {
// assert checked in new()
skip_assert_initialized!();
let mut ret = IdStr::new();
ret.set(value);
ret
}
}
impl From<&GString> for IdStr {
#[inline]
fn from(value: &GString) -> IdStr {
skip_assert_initialized!();
let mut ret = IdStr::new();
ret.set(value);
ret
}
}
impl From<GString> for IdStr {
#[inline]
fn from(value: GString) -> IdStr {
skip_assert_initialized!();
let mut ret = IdStr::new();
ret.set(&value);
ret
}
}
impl fmt::Display for IdStr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_gstr())
}
}
impl PartialOrd for IdStr {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for IdStr {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.is_equal(other)
}
}
impl PartialOrd<&IdStr> for IdStr {
#[inline]
fn partial_cmp(&self, other: &&IdStr) -> Option<cmp::Ordering> {
Some(self.cmp(*other))
}
}
impl PartialEq<&IdStr> for IdStr {
#[inline]
fn eq(&self, other: &&IdStr) -> bool {
self.is_equal(other)
}
}
impl Ord for IdStr {
#[inline]
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.as_cstr().cmp(other.as_cstr())
}
}
impl Eq for IdStr {}
impl PartialOrd<&GStr> for IdStr {
#[inline]
fn partial_cmp(&self, other: &&GStr) -> Option<cmp::Ordering> {
self.as_str().partial_cmp(*other)
}
}
impl PartialEq<&GStr> for IdStr {
#[inline]
fn eq(&self, other: &&GStr) -> bool {
self.is_equal_to_str(other)
}
}
impl PartialOrd<GStr> for IdStr {
#[inline]
fn partial_cmp(&self, other: &GStr) -> Option<cmp::Ordering> {
self.as_str().partial_cmp(other)
}
}
impl PartialEq<GStr> for IdStr {
#[inline]
fn eq(&self, other: &GStr) -> bool {
self.is_equal_to_str(other)
}
}
impl PartialOrd<IdStr> for &GStr {
#[inline]
fn partial_cmp(&self, other: &IdStr) -> Option<cmp::Ordering> {
(*self).partial_cmp(other.as_gstr())
}
}
impl PartialEq<IdStr> for &GStr {
#[inline]
fn eq(&self, other: &IdStr) -> bool {
other.is_equal_to_str(self)
}
}
impl PartialOrd<IdStr> for GStr {
#[inline]
fn partial_cmp(&self, other: &IdStr) -> Option<cmp::Ordering> {
self.partial_cmp(other.as_gstr())
}
}
impl PartialEq<IdStr> for GStr {
#[inline]
fn eq(&self, other: &IdStr) -> bool {
other.is_equal_to_str(self)
}
}
impl PartialOrd<&str> for IdStr {
#[inline]
fn partial_cmp(&self, other: &&str) -> Option<cmp::Ordering> {
self.as_gstr().partial_cmp(*other)
}
}
impl PartialEq<&str> for IdStr {
#[inline]
fn eq(&self, other: &&str) -> bool {
self.is_equal_to_str(*other)
}
}
impl PartialOrd<str> for IdStr {
#[inline]
fn partial_cmp(&self, other: &str) -> Option<cmp::Ordering> {
self.as_gstr().partial_cmp(other)
}
}
impl PartialEq<str> for IdStr {
#[inline]
fn eq(&self, other: &str) -> bool {
self.is_equal_to_str(other)
}
}
impl PartialOrd<IdStr> for &str {
#[inline]
fn partial_cmp(&self, other: &IdStr) -> Option<cmp::Ordering> {
(*self).partial_cmp(other.as_gstr())
}
}
impl PartialEq<IdStr> for &str {
#[inline]
fn eq(&self, other: &IdStr) -> bool {
other.is_equal_to_str(self)
}
}
impl PartialOrd<IdStr> for str {
#[inline]
fn partial_cmp(&self, other: &IdStr) -> Option<cmp::Ordering> {
self.partial_cmp(other.as_gstr())
}
}
impl PartialEq<IdStr> for str {
#[inline]
fn eq(&self, other: &IdStr) -> bool {
other.is_equal_to_str(self)
}
}
impl PartialOrd<GString> for IdStr {
#[inline]
fn partial_cmp(&self, other: &GString) -> Option<cmp::Ordering> {
self.as_gstr().partial_cmp(other)
}
}
impl PartialEq<GString> for IdStr {
#[inline]
fn eq(&self, other: &GString) -> bool {
self.is_equal_to_str(other)
}
}
impl PartialOrd<IdStr> for GString {
#[inline]
fn partial_cmp(&self, other: &IdStr) -> Option<cmp::Ordering> {
self.partial_cmp(other.as_gstr())
}
}
impl PartialEq<IdStr> for GString {
#[inline]
fn eq(&self, other: &IdStr) -> bool {
other.is_equal_to_str(self)
}
}
impl PartialOrd<String> for IdStr {
#[inline]
fn partial_cmp(&self, other: &String) -> Option<cmp::Ordering> {
self.as_gstr().partial_cmp(other)
}
}
impl PartialEq<String> for IdStr {
#[inline]
fn eq(&self, other: &String) -> bool {
self.is_equal_to_str(other)
}
}
impl PartialOrd<IdStr> for String {
#[inline]
fn partial_cmp(&self, other: &IdStr) -> Option<cmp::Ordering> {
self.partial_cmp(other.as_gstr())
}
}
impl PartialEq<IdStr> for String {
#[inline]
fn eq(&self, other: &IdStr) -> bool {
other.is_equal_to_str(self)
}
}
impl Hash for IdStr {
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_gstr().hash(state)
}
}
unsafe impl Send for IdStr {}
unsafe impl Sync for IdStr {}
// Tests are mutualised between this implementation and the one in id_str_compat
// See gstreamer/id_str/mod.rs

View file

@ -0,0 +1,543 @@
// Take a look at the license at the top of the repository in the LICENSE file.
// rustdoc-stripper-ignore-next
//! The `IdStr` compatibility implementation.
//!
//! See the higher level module documentation for details.
use glib::{GStr, GString, IntoGStr};
use std::{
cmp,
ffi::CStr,
fmt,
hash::{Hash, Hasher},
ops::Deref,
};
use kstring::KString;
// rustdoc-stripper-ignore-next
/// An UTF-8 immutable string type with optimizations for short values (len < 16).
#[derive(Clone, Debug)]
#[doc(alias = "GstIdStr")]
pub struct IdStr(KString);
impl IdStr {
// In order to keep the same API and usability as `id_str_bindings::IdStr` regarding
// the ability to efficiently deref to `&GStr`, the internal `KString` is always built
// from a string with a nul terminator.
#[doc(alias = "gst_id_str_new")]
#[inline]
pub const fn new() -> IdStr {
skip_assert_initialized!();
// Always include the nul terminator in the internal string
IdStr(KString::from_static("\0"))
}
// rustdoc-stripper-ignore-next
/// Builds an `IdStr` from the given static `GStr`.
///
/// This constructor performs optimizations which other constructors can't rely on.
///
/// To build an `IdStr` from a string literal, use the [`idstr`](crate::idstr) macro.
#[inline]
pub fn from_static<T: AsRef<GStr> + ?Sized>(value: &'static T) -> IdStr {
skip_assert_initialized!();
let gstr = value.as_ref();
unsafe {
let str_with_nul = std::str::from_utf8_unchecked(std::slice::from_raw_parts(
gstr.as_ptr() as *const _,
gstr.as_bytes_with_nul().len(),
));
IdStr(KString::from_static(str_with_nul))
}
}
#[doc(alias = "gst_id_str_new")]
#[inline]
pub fn from(value: impl AsRef<str>) -> IdStr {
skip_assert_initialized!();
let mut id = IdStr::new();
id.set(value);
id
}
#[doc(alias = "gst_id_str_get_len")]
#[inline]
pub fn len(&self) -> usize {
// The internal string ends with a nul terminator
self.0.len() - 1
}
#[inline]
pub fn is_empty(&self) -> bool {
// The internal string ends with a nul terminator
self.0.len() == 1
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
// The internal string ends with a nul terminator
&self.0.as_bytes()[..IdStr::len(self)]
}
#[inline]
fn as_bytes_with_nul(&self) -> &[u8] {
// The internal string ends with a nul terminator
self.0.as_bytes()
}
#[inline]
pub fn as_str(&self) -> &str {
unsafe {
// Safety: the internal value is guaranteed to be an utf-8 string.
std::str::from_utf8_unchecked(self.as_bytes())
}
}
#[doc(alias = "gst_id_str_as_str")]
#[inline]
pub fn as_gstr(&self) -> &GStr {
unsafe {
// Safety: the internal value is guaranteed to be an utf-8 string.
GStr::from_utf8_with_nul_unchecked(self.as_bytes_with_nul())
}
}
#[doc(alias = "gst_id_str_as_str")]
#[inline]
pub fn as_cstr(&self) -> &CStr {
unsafe {
// Safety: the internal value is guaranteed to be an utf-8 string
// thus to not contain any nul bytes except for the terminator.
CStr::from_bytes_with_nul_unchecked(self.as_bytes_with_nul())
}
}
// rustdoc-stripper-ignore-next
/// Sets `self` to the static string `value`.
///
/// This function performs optimizations which [IdStr::set] can't rely on.
///
/// To build an `IdStr` from a string literal, use the [`idstr`](crate::idstr) macro.
#[doc(alias = "gst_id_str_set_static_str")]
#[doc(alias = "gst_id_str_set_static_str_with_len")]
#[inline]
pub fn set_static<T: AsRef<GStr> + ?Sized>(&mut self, value: &'static T) {
unsafe {
let gstr = value.as_ref();
// Safety: the `GStr` value is guaranteed to be an utf-8 string
// ending with a nul terminator.
let str_with_nul = std::str::from_utf8_unchecked(std::slice::from_raw_parts(
gstr.as_ptr() as *const _,
gstr.as_bytes_with_nul().len(),
));
self.0 = KString::from_static(str_with_nul);
}
}
// rustdoc-stripper-ignore-next
/// Sets `self` to the string `value`.
///
/// For a static value, use [IdStr::set_static] which can perform optimizations.
///
/// To build an `IdStr` from a string literal, use the [`idstr`](crate::idstr) macro.
#[doc(alias = "gst_id_str_set")]
#[doc(alias = "gst_id_str_set_with_len")]
#[inline]
pub fn set(&mut self, value: impl AsRef<str>) {
self.0 = value.as_ref().run_with_gstr(|gstr| unsafe {
let str_with_nul = std::str::from_utf8_unchecked(std::slice::from_raw_parts(
gstr.as_ptr() as *const _,
gstr.as_bytes_with_nul().len(),
));
KString::from_ref(str_with_nul)
});
}
}
impl Default for IdStr {
fn default() -> Self {
Self::new()
}
}
impl Deref for IdStr {
type Target = GStr;
fn deref(&self) -> &Self::Target {
self.as_gstr()
}
}
impl AsRef<IdStr> for IdStr {
#[inline]
fn as_ref(&self) -> &IdStr {
self
}
}
impl AsRef<str> for IdStr {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsRef<GStr> for IdStr {
#[inline]
fn as_ref(&self) -> &GStr {
self.as_gstr()
}
}
impl AsRef<CStr> for IdStr {
#[inline]
fn as_ref(&self) -> &CStr {
self.as_cstr()
}
}
impl From<&str> for IdStr {
#[inline]
fn from(value: &str) -> IdStr {
skip_assert_initialized!();
value.run_with_gstr(|gstr| unsafe {
// Safety: the `GStr` value is guaranteed to be an utf-8 string
// ending with a nul terminator.
let str_with_nul = std::str::from_utf8_unchecked(std::slice::from_raw_parts(
gstr.as_ptr() as *const _,
gstr.as_bytes_with_nul().len(),
));
IdStr(KString::from_ref(str_with_nul))
})
}
}
impl From<&String> for IdStr {
#[inline]
fn from(value: &String) -> IdStr {
skip_assert_initialized!();
value.run_with_gstr(|gstr| unsafe {
// Safety: the `GStr` value is guaranteed to be an utf-8 string
// ending with a nul terminator.
let str_with_nul = std::str::from_utf8_unchecked(std::slice::from_raw_parts(
gstr.as_ptr() as *const _,
gstr.as_bytes_with_nul().len(),
));
IdStr(KString::from_ref(str_with_nul))
})
}
}
impl From<String> for IdStr {
#[inline]
fn from(value: String) -> IdStr {
skip_assert_initialized!();
value.run_with_gstr(|gstr| unsafe {
// Safety: the `GStr` value is guaranteed to be an utf-8 string
// ending with a nul terminator.
let str_with_nul = std::str::from_utf8_unchecked(std::slice::from_raw_parts(
gstr.as_ptr() as *const _,
gstr.as_bytes_with_nul().len(),
));
IdStr(KString::from_ref(str_with_nul))
})
}
}
impl From<&GStr> for IdStr {
#[inline]
fn from(value: &GStr) -> IdStr {
skip_assert_initialized!();
unsafe {
// Safety: the `GStr` value is guaranteed to be an utf-8 string
// ending with a nul terminator.
let str_with_nul = std::str::from_utf8_unchecked(std::slice::from_raw_parts(
value.as_ptr() as *const _,
value.as_bytes_with_nul().len(),
));
IdStr(KString::from_ref(str_with_nul))
}
}
}
impl From<&GString> for IdStr {
#[inline]
fn from(value: &GString) -> IdStr {
skip_assert_initialized!();
unsafe {
// Safety: the `GString` value is guaranteed to be an utf-8 string
// ending with a nul terminator.
let str_with_nul = std::str::from_utf8_unchecked(std::slice::from_raw_parts(
value.as_ptr() as *const _,
value.len() + 1,
));
IdStr(KString::from_ref(str_with_nul))
}
}
}
impl From<GString> for IdStr {
#[inline]
fn from(value: GString) -> IdStr {
skip_assert_initialized!();
unsafe {
// Safety: the `GString` value is guaranteed to be an utf-8 string
// ending with a nul terminator.
let str_with_nul = std::str::from_utf8_unchecked(std::slice::from_raw_parts(
value.as_ptr() as *const _,
value.len() + 1,
));
IdStr(KString::from_ref(str_with_nul))
}
}
}
impl fmt::Display for IdStr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_gstr())
}
}
impl PartialOrd for IdStr {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for IdStr {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl PartialOrd<&IdStr> for IdStr {
#[inline]
fn partial_cmp(&self, other: &&IdStr) -> Option<cmp::Ordering> {
Some(self.0.cmp(&other.0))
}
}
impl PartialEq<&IdStr> for IdStr {
#[inline]
fn eq(&self, other: &&IdStr) -> bool {
self.0 == other.0
}
}
impl PartialOrd<IdStr> for &IdStr {
#[inline]
fn partial_cmp(&self, other: &IdStr) -> Option<cmp::Ordering> {
Some(self.0.cmp(&other.0))
}
}
impl PartialEq<IdStr> for &IdStr {
#[inline]
fn eq(&self, other: &IdStr) -> bool {
self.0 == other.0
}
}
impl Ord for IdStr {
#[inline]
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.0.cmp(&other.0)
}
}
impl Eq for IdStr {}
impl PartialOrd<&GStr> for IdStr {
#[inline]
fn partial_cmp(&self, other: &&GStr) -> Option<cmp::Ordering> {
self.as_gstr().partial_cmp(*other)
}
}
impl PartialEq<&GStr> for IdStr {
#[inline]
fn eq(&self, other: &&GStr) -> bool {
self.as_gstr() == *other
}
}
impl PartialOrd<GStr> for IdStr {
#[inline]
fn partial_cmp(&self, other: &GStr) -> Option<cmp::Ordering> {
self.as_gstr().partial_cmp(other)
}
}
impl PartialEq<GStr> for IdStr {
#[inline]
fn eq(&self, other: &GStr) -> bool {
self.as_gstr() == other
}
}
impl PartialOrd<IdStr> for &GStr {
#[inline]
fn partial_cmp(&self, other: &IdStr) -> Option<cmp::Ordering> {
(*self).partial_cmp(other.as_gstr())
}
}
impl PartialEq<IdStr> for &GStr {
#[inline]
fn eq(&self, other: &IdStr) -> bool {
(*self) == other.as_gstr()
}
}
impl PartialOrd<IdStr> for GStr {
#[inline]
fn partial_cmp(&self, other: &IdStr) -> Option<cmp::Ordering> {
self.partial_cmp(other.as_gstr())
}
}
impl PartialEq<IdStr> for GStr {
#[inline]
fn eq(&self, other: &IdStr) -> bool {
self == other.as_gstr()
}
}
impl PartialOrd<&str> for IdStr {
#[inline]
fn partial_cmp(&self, other: &&str) -> Option<cmp::Ordering> {
self.as_gstr().partial_cmp(*other)
}
}
impl PartialEq<&str> for IdStr {
#[inline]
fn eq(&self, other: &&str) -> bool {
self.as_gstr() == *other
}
}
impl PartialOrd<str> for IdStr {
#[inline]
fn partial_cmp(&self, other: &str) -> Option<cmp::Ordering> {
self.as_gstr().partial_cmp(other)
}
}
impl PartialEq<str> for IdStr {
#[inline]
fn eq(&self, other: &str) -> bool {
self.as_gstr() == other
}
}
impl PartialOrd<IdStr> for &str {
#[inline]
fn partial_cmp(&self, other: &IdStr) -> Option<cmp::Ordering> {
(*self).partial_cmp(other.as_gstr())
}
}
impl PartialEq<IdStr> for &str {
#[inline]
fn eq(&self, other: &IdStr) -> bool {
(*self) == other.as_gstr()
}
}
impl PartialOrd<IdStr> for str {
#[inline]
fn partial_cmp(&self, other: &IdStr) -> Option<cmp::Ordering> {
self.partial_cmp(other.as_gstr())
}
}
impl PartialEq<IdStr> for str {
#[inline]
fn eq(&self, other: &IdStr) -> bool {
self == other.as_gstr()
}
}
impl PartialOrd<GString> for IdStr {
#[inline]
fn partial_cmp(&self, other: &GString) -> Option<cmp::Ordering> {
self.as_gstr().partial_cmp(other)
}
}
impl PartialEq<GString> for IdStr {
#[inline]
fn eq(&self, other: &GString) -> bool {
self.as_gstr() == other
}
}
impl PartialOrd<IdStr> for GString {
#[inline]
fn partial_cmp(&self, other: &IdStr) -> Option<cmp::Ordering> {
self.partial_cmp(other.as_gstr())
}
}
impl PartialEq<IdStr> for GString {
#[inline]
fn eq(&self, other: &IdStr) -> bool {
self == other.as_gstr()
}
}
impl PartialOrd<String> for IdStr {
#[inline]
fn partial_cmp(&self, other: &String) -> Option<cmp::Ordering> {
self.as_gstr().partial_cmp(other)
}
}
impl PartialEq<String> for IdStr {
#[inline]
fn eq(&self, other: &String) -> bool {
self.as_gstr() == other
}
}
impl PartialOrd<IdStr> for String {
#[inline]
fn partial_cmp(&self, other: &IdStr) -> Option<cmp::Ordering> {
self.partial_cmp(other.as_gstr())
}
}
impl PartialEq<IdStr> for String {
#[inline]
fn eq(&self, other: &IdStr) -> bool {
self == other.as_gstr()
}
}
impl Hash for IdStr {
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_gstr().hash(state)
}
}
unsafe impl Send for IdStr {}
unsafe impl Sync for IdStr {}
// Tests are mutualised between this implementation and the one in id_str_bindings
// See gstreamer/id_str/mod.rs

435
gstreamer/src/id_str/mod.rs Normal file
View file

@ -0,0 +1,435 @@
// Take a look at the license at the top of the repository in the LICENSE file.
// rustdoc-stripper-ignore-next
//! This module selects one of the two `IdStr` implementations:
//!
//! * When feature `v1_26` (or later) is activated, `IdStr` implements the bindings
//! for the C type `GstIdStr`.
//! * For earlier feature versions, a compatibility implementation is used.
//!
//! See also: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/7432>
cfg_if::cfg_if! {
if #[cfg(feature = "v1_26")] {
mod bindings;
pub use self::bindings::IdStr;
} else {
mod compat;
pub use self::compat::IdStr;
}
}
#[cfg(feature = "serde")]
mod serde;
// rustdoc-stripper-ignore-next
/// Builds an [`IdStr`] from a string literal.
///
/// # Examples
///
/// ```
/// # fn main() {
/// use gstreamer::{idstr, IdStr};
/// use std::sync::LazyLock;
///
/// static MY_ID_STR: LazyLock<IdStr> = LazyLock::new(|| idstr!("static id"));
/// assert_eq!(*MY_ID_STR, "static id");
///
/// let my_id_str: IdStr = idstr!("local id");
/// assert_eq!(my_id_str, "local id");
/// # }
/// ```
///
/// [`IdStr`]: crate::IdStr
#[macro_export]
macro_rules! idstr {
($s:literal) => {
IdStr::from_static($crate::glib::gstr!($s))
};
}
#[cfg(test)]
mod tests {
use glib::{gstr, GStr, GString};
use std::{ffi::CStr, sync::LazyLock};
use super::IdStr;
const STR: &str = "STR";
static IDSTR: LazyLock<IdStr> = LazyLock::new(|| idstr!("IDSTR"));
static GSTR: &GStr = gstr!("GSTR");
static GSTRING: LazyLock<GString> = LazyLock::new(|| GString::from("GSTRING"));
const LONG_STR: &str = "An STR longer than 15 bytes";
static LONG_IDSTR: LazyLock<IdStr> = LazyLock::new(|| idstr!("An IdStr longer than 15 bytes"));
static LONG_GSTR: &GStr = gstr!("A GSTR longer than 15 bytes");
static LONG_GSTRING: LazyLock<GString> =
LazyLock::new(|| GString::from("A GSTRING longer than 15 bytes"));
#[test]
fn new_set_static() {
assert!(!IDSTR.is_empty());
assert_eq!(IDSTR.len(), "IDSTR".len());
assert_eq!(IDSTR.as_str().len(), "IDSTR".len());
assert_eq!(*IDSTR, "IDSTR");
// Display impl
assert_eq!(IDSTR.to_string(), "IDSTR");
assert_eq!(IDSTR.as_str(), "IDSTR");
assert_eq!(IDSTR.as_gstr().len(), "IDSTR".len());
assert_eq!(IDSTR.as_gstr(), "IDSTR");
let id_str: IdStr = idstr!("id_str");
assert!(!id_str.is_empty());
assert_eq!(id_str.len(), "id_str".len());
assert_eq!(id_str.as_str().len(), "id_str".len());
assert_eq!(id_str, "id_str");
let mut s = IdStr::new();
assert!(s.is_empty());
assert_eq!(s.len(), 0);
assert_eq!(s.as_str(), "");
assert_eq!(s.as_gstr(), "");
s.set_static(gstr!("str"));
assert!(!s.is_empty());
assert_eq!(s.len(), "str".len());
assert_eq!(s.as_str().len(), "str".len());
assert_eq!(s, "str");
// Display impl
assert_eq!(s.to_string(), "str");
assert_eq!(s.as_str(), "str");
assert_eq!(s.as_gstr().len(), "str".len());
assert_eq!(s.as_gstr(), "str");
s.set_static(GSTR);
assert_eq!(s.as_str(), "GSTR");
s.set_static(&*GSTRING);
assert_eq!(s.as_str(), "GSTRING");
assert!(!LONG_IDSTR.is_empty());
assert_eq!(LONG_IDSTR.len(), "An IdStr longer than 15 bytes".len());
assert_eq!(*LONG_IDSTR, "An IdStr longer than 15 bytes");
// Display impl
assert_eq!(LONG_IDSTR.to_string(), "An IdStr longer than 15 bytes");
assert_eq!(
LONG_IDSTR.as_str().len(),
"An IdStr longer than 15 bytes".len()
);
assert_eq!(LONG_IDSTR.as_str(), "An IdStr longer than 15 bytes");
assert_eq!(
LONG_IDSTR.as_gstr().len(),
"An IdStr longer than 15 bytes".len()
);
assert_eq!(LONG_IDSTR.as_gstr(), "An IdStr longer than 15 bytes");
let ls = idstr!("An IdStr longer than 15 bytes");
assert!(!ls.is_empty());
assert_eq!(ls.len(), "An IdStr longer than 15 bytes".len());
assert_eq!(ls, "An IdStr longer than 15 bytes");
let mut ls = IdStr::new();
ls.set_static(gstr!("An str longer than 15 bytes"));
assert!(!ls.is_empty());
assert_eq!(ls.len(), "An str longer than 15 bytes".len());
assert_eq!(ls, "An str longer than 15 bytes");
ls.set_static(LONG_GSTR);
assert_eq!(ls.as_str(), "A GSTR longer than 15 bytes");
ls.set_static(&*LONG_GSTRING);
assert_eq!(ls.as_str(), "A GSTRING longer than 15 bytes");
}
#[test]
fn from_static() {
let s = IdStr::from_static(gstr!("str"));
assert!(!s.is_empty());
assert_eq!(s.len(), "str".len());
assert_eq!(s.as_str().len(), "str".len());
assert_eq!(s, "str");
// Display impl
assert_eq!(s.to_string(), "str");
assert_eq!(s.as_str(), "str");
assert_eq!(s.as_gstr().len(), "str".len());
assert_eq!(s.as_gstr(), "str");
let s = idstr!("str");
assert!(!s.is_empty());
assert_eq!(s.len(), "str".len());
assert_eq!(s.as_str().len(), "str".len());
assert_eq!(s, "str");
let s = IdStr::from_static(GSTR);
assert_eq!(s.as_str(), "GSTR");
let s = IdStr::from_static(&*GSTRING);
assert_eq!(s.as_str(), "GSTRING");
let ls = IdStr::from_static(gstr!("An str longer than 15 bytes"));
assert!(!ls.is_empty());
assert_eq!(ls.len(), "An str longer than 15 bytes".len());
assert_eq!(ls, "An str longer than 15 bytes");
// Display impl
assert_eq!(ls.to_string(), "An str longer than 15 bytes");
assert_eq!(ls.as_str().len(), "An str longer than 15 bytes".len());
assert_eq!(ls.as_str(), "An str longer than 15 bytes");
assert_eq!(ls.as_gstr().len(), "An str longer than 15 bytes".len());
assert_eq!(ls.as_gstr(), "An str longer than 15 bytes");
let ls = idstr!("An str longer than 15 bytes");
assert!(!ls.is_empty());
assert_eq!(ls.len(), "An str longer than 15 bytes".len());
assert_eq!(ls, "An str longer than 15 bytes");
let ls = IdStr::from_static(LONG_GSTR);
assert_eq!(ls.as_str(), "A GSTR longer than 15 bytes");
let ls = IdStr::from_static(&*LONG_GSTRING);
assert_eq!(ls.as_str(), "A GSTRING longer than 15 bytes");
}
#[test]
fn new_set() {
let d = IdStr::default();
assert!(d.is_empty());
assert_eq!(d.len(), 0);
assert_eq!(d.as_str(), "");
assert_eq!(d.as_gstr(), "");
let mut s = IdStr::new();
assert!(s.is_empty());
assert_eq!(s.len(), 0);
assert_eq!(s.as_str(), "");
assert_eq!(s.as_gstr(), "");
s.set("str");
assert!(!s.is_empty());
assert_eq!(s.len(), "str".len());
assert_eq!(s.as_str().len(), "str".len());
assert_eq!(s.as_str(), "str");
assert_eq!(AsRef::<str>::as_ref(&s), "str");
// Display impl
assert_eq!(s.to_string(), "str");
assert_eq!(s.as_gstr().len(), "str".len());
assert_eq!(s.as_gstr(), "str");
assert_eq!(AsRef::<GStr>::as_ref(&s), "str");
assert_eq!(s.as_cstr().to_bytes(), b"str");
assert_eq!(AsRef::<CStr>::as_ref(&s).to_bytes(), b"str");
assert_eq!(s.as_bytes(), b"str");
let string = String::from("String");
s.set(string.as_str());
assert_eq!(s.as_str(), "String");
s.set(&string);
assert_eq!(s.as_str(), "String");
s.set(gstr!("gstr"));
assert_eq!(s.as_str(), "gstr");
let gstring = GString::from("GString");
s.set(gstring.as_gstr());
assert_eq!(s.as_str(), "GString");
s.set(&gstring);
assert_eq!(s.as_str(), "GString");
s.set(gstring.as_str());
assert_eq!(s.as_str(), "GString");
let mut ls = IdStr::new();
ls.set("An str longer than 15 bytes");
assert!(!ls.is_empty());
assert_eq!(ls.len(), "An str longer than 15 bytes".len());
assert_eq!(ls, "An str longer than 15 bytes");
// Display impl
assert_eq!(ls.to_string(), "An str longer than 15 bytes");
assert_eq!(ls.as_str().len(), "An str longer than 15 bytes".len());
assert_eq!(ls.as_str(), "An str longer than 15 bytes");
assert_eq!(ls.as_gstr().len(), "An str longer than 15 bytes".len());
assert_eq!(ls.as_gstr(), "An str longer than 15 bytes");
assert_eq!(ls.as_cstr().to_bytes(), b"An str longer than 15 bytes");
assert_eq!(
AsRef::<CStr>::as_ref(&ls).to_bytes(),
b"An str longer than 15 bytes"
);
assert_eq!(ls.as_bytes(), b"An str longer than 15 bytes");
ls.set(gstr!("A gstr longer than 15 bytes"));
assert_eq!(ls.as_str(), "A gstr longer than 15 bytes");
}
#[test]
fn from() {
let s = IdStr::from("str");
assert_eq!(s.len(), "str".len());
assert_eq!(s.as_str().len(), "str".len());
assert_eq!(s.as_str(), "str");
// Display impl
assert_eq!(s.to_string(), "str");
assert_eq!(s.as_gstr().len(), "str".len());
assert_eq!(s.as_gstr(), "str");
let string = String::from("String");
let s = IdStr::from(string.as_str());
assert_eq!(s.as_str(), "String");
let s: IdStr = string.as_str().into();
assert_eq!(s.as_str(), "String");
let s: IdStr = (&string).into();
assert_eq!(s.as_str(), "String");
let s: IdStr = string.into();
assert_eq!(s.as_str(), "String");
let s = IdStr::from(gstr!("str"));
assert_eq!(s.as_str(), "str");
let gstring = GString::from("GString");
let s = IdStr::from(gstring.as_gstr());
assert_eq!(s.as_str(), "GString");
let s: IdStr = (&gstring).into();
assert_eq!(s.as_str(), "GString");
let s: IdStr = gstring.into();
assert_eq!(s.as_str(), "GString");
let ls = IdStr::from("An str longer than 15 bytes");
assert!(!ls.is_empty());
assert_eq!(ls.len(), "An str longer than 15 bytes".len());
assert_eq!(ls, "An str longer than 15 bytes");
// Display impl
assert_eq!(ls.to_string(), "An str longer than 15 bytes");
assert_eq!(ls.as_str().len(), "An str longer than 15 bytes".len());
assert_eq!(ls.as_str(), "An str longer than 15 bytes");
assert_eq!(ls.as_gstr().len(), "An str longer than 15 bytes".len());
assert_eq!(ls.as_gstr(), "An str longer than 15 bytes");
let ls = IdStr::from(gstr!("A gstr longer than 15 bytes"));
assert_eq!(ls.as_str(), "A gstr longer than 15 bytes");
assert_eq!(ls.as_gstr(), "A gstr longer than 15 bytes");
let lstring = String::from("A String longer than 15 bytes");
let ls = IdStr::from(lstring.as_str());
assert_eq!(ls.as_str(), "A String longer than 15 bytes");
let ls = IdStr::from(&lstring);
assert_eq!(ls.as_str(), "A String longer than 15 bytes");
let lgstring = String::from("A GString longer than 15 bytes");
let ls = IdStr::from(lgstring.as_str());
assert_eq!(ls.as_str(), "A GString longer than 15 bytes");
let ls = IdStr::from(&lgstring);
assert_eq!(ls.as_str(), "A GString longer than 15 bytes");
}
#[test]
#[allow(clippy::cmp_owned)]
fn eq_cmp() {
let s1 = IdStr::from(STR);
let s12: IdStr = STR.into();
assert_eq!(s1, s12);
let s2 = IdStr::from(String::from(STR));
let s22: IdStr = String::from(STR).into();
assert_eq!(s2, s22);
let s3 = IdStr::from_static(gstr!("STR"));
assert_eq!(s1, s2);
assert_eq!(s1, s3);
assert_eq!(s2, s3);
assert!(s1 == gstr!("STR"));
assert_eq!(s1, gstr!("STR"));
assert_eq!(s1, GString::from("STR"));
assert!(s1 == "STR");
assert_eq!(s1, "STR");
assert!("STR" == s1);
assert_eq!("STR", s1);
assert_eq!(s1, String::from("STR"));
assert_eq!(gstr!("STR"), s1);
assert_eq!(GString::from("STR"), s1);
assert_eq!("STR", s1);
assert_eq!(String::from("STR"), s1);
let ls1 = IdStr::from(LONG_STR);
let ls2: IdStr = String::from(LONG_STR).into();
let ls3 = IdStr::from_static(gstr!("An STR longer than 15 bytes"));
assert_eq!(ls1, ls2);
assert_eq!(ls1, ls3);
assert_eq!(ls2, ls3);
assert!(ls1 == gstr!("An STR longer than 15 bytes"));
assert_eq!(ls1, gstr!("An STR longer than 15 bytes"));
assert_eq!(ls1, GString::from(LONG_STR));
assert_eq!(ls1, LONG_STR);
assert!(ls1 == "An STR longer than 15 bytes");
assert_eq!(ls1, "An STR longer than 15 bytes");
assert_eq!(ls1, String::from(LONG_STR));
assert_eq!(gstr!("An STR longer than 15 bytes"), ls1);
assert_eq!(GString::from(LONG_STR), ls1);
assert_eq!(LONG_STR, ls1);
assert_eq!("An STR longer than 15 bytes", ls1);
assert_eq!(String::from(LONG_STR), ls1);
assert_ne!(s1, ls1);
assert_ne!(ls1, s1);
let s4 = IdStr::from("STR4");
assert_ne!(s1, s4);
assert!(s1 < s4);
assert!(s4 > s1);
assert!(s1 < gstr!("STR4"));
assert!(s1 < GString::from("STR4"));
assert!(s1 < "STR4");
assert!("STR4" > s1);
assert!(s1 < String::from("STR4"));
assert!(gstr!("STR4") > s1);
assert!(GString::from("STR4") > s1);
assert!("STR4" > s1);
assert!(String::from("STR4") > s1);
// ls1 starts with an 'A', s4 with an 'S'
assert_ne!(ls1, s4);
assert!(ls1 < s4);
assert!(s4 > s1);
}
#[test]
fn as_ref_idstr() {
#[allow(clippy::nonminimal_bool)]
fn check(c: &str, v: impl AsRef<IdStr>) {
let v = v.as_ref();
assert_eq!(c, v);
assert_eq!(v, c);
assert!(!(c > v));
assert!(!(v > c));
let i = IdStr::from(c);
assert_eq!(i, v);
assert_eq!(i, IdStr::from(c));
assert!(!(c > i));
assert!(!(i > c));
}
let v = IdStr::from(STR);
check(STR, &v);
check(STR, v);
#[allow(clippy::nonminimal_bool)]
fn check_gstr(c: &GStr, v: impl AsRef<IdStr>) {
let v = v.as_ref();
assert_eq!(c, v);
assert_eq!(v, c);
assert!(!(c > v));
assert!(!(v > c));
}
let v = IdStr::from(GSTR);
check_gstr(GSTR, &v);
check_gstr(GSTR, v);
}
}

View file

@ -0,0 +1,37 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use serde::{
de::{Deserialize, Deserializer},
ser::{Serialize, Serializer},
};
use crate::IdStr;
impl Serialize for IdStr {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.as_str().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for IdStr {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
skip_assert_initialized!();
<&str>::deserialize(deserializer).map(IdStr::from)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::idstr;
#[test]
fn ser_de() {
assert_eq!(
ron::ser::to_string(&idstr!("my IdStr")),
Ok("\"my IdStr\"".to_owned())
);
assert_eq!(ron::de::from_str("\"my IdStr\""), Ok(idstr!("my IdStr")));
}
}

View file

@ -82,6 +82,10 @@ pub use crate::value::{
#[macro_use] #[macro_use]
mod value_serde; mod value_serde;
#[macro_use]
mod id_str;
pub use crate::id_str::IdStr;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
mod flag_serde; mod flag_serde;