diff --git a/crates/rb-sys-tests/src/lib.rs b/crates/rb-sys-tests/src/lib.rs index 8b823e5e..8c96782c 100644 --- a/crates/rb-sys-tests/src/lib.rs +++ b/crates/rb-sys-tests/src/lib.rs @@ -25,3 +25,6 @@ mod memory_test; #[cfg(test)] mod stable_api_test; + +#[cfg(test)] +mod symbol_test; diff --git a/crates/rb-sys-tests/src/symbol_test.rs b/crates/rb-sys-tests/src/symbol_test.rs new file mode 100644 index 00000000..92ee6d75 --- /dev/null +++ b/crates/rb-sys-tests/src/symbol_test.rs @@ -0,0 +1,45 @@ +use std::slice; + +use rb_sys::{ + rb_funcall, rb_id2sym, rb_intern, rb_utf8_str_new, RSTRING_LEN, RSTRING_PTR, STATIC_SYM_P, +}; +use rb_sys_test_helpers::ruby_test; + +#[ruby_test] +fn test_creates_a_usable_id() { + let method_name = unsafe { rb_intern!("reverse") }; + + let mystring = unsafe { rb_utf8_str_new("jerrbear".as_ptr() as *mut _, 8) }; + let ret = unsafe { rb_funcall(mystring, method_name, 0) }; + let ptr = unsafe { RSTRING_PTR(ret) as *const u8 }; + let len = unsafe { RSTRING_LEN(ret) } as _; + let result = unsafe { slice::from_raw_parts(ptr, len) }; + + assert_eq!(result, b"raebrrej"); +} + +#[ruby_test] +fn test_has_repeatable_results() { + let method_name1 = unsafe { rb_intern!("reverse") }; + let method_name2 = unsafe { rb_intern!("reverse") }; + + assert_ne!(method_name1, 0); + assert_ne!(method_name2, 0); + assert_eq!(method_name1, method_name2); +} + +#[ruby_test] +fn test_non_usascii() { + let method_name1 = unsafe { rb_intern!("🙈") }; + let method_name2 = unsafe { rb_intern!("🙈") }; + + assert_ne!(method_name1, 0); + assert_ne!(method_name2, 0); + assert_eq!(method_name1, method_name2); + + let sym1 = unsafe { rb_id2sym(method_name1) }; + let sym2 = unsafe { rb_id2sym(method_name2) }; + + assert!(STATIC_SYM_P(sym1)); + assert!(STATIC_SYM_P(sym2)); +} diff --git a/crates/rb-sys/src/lib.rs b/crates/rb-sys/src/lib.rs index 181aae62..bf8c918b 100644 --- a/crates/rb-sys/src/lib.rs +++ b/crates/rb-sys/src/lib.rs @@ -8,6 +8,7 @@ pub mod memory; pub mod special_consts; #[cfg(feature = "stable-api")] pub mod stable_api; +pub mod symbol; pub mod tracking_allocator; pub mod value_type; diff --git a/crates/rb-sys/src/symbol.rs b/crates/rb-sys/src/symbol.rs new file mode 100644 index 00000000..41de85f6 --- /dev/null +++ b/crates/rb-sys/src/symbol.rs @@ -0,0 +1,31 @@ +/// Finds or creates a symbol for the given static string. This macro will +/// memoize the ID to avoid repeated calls to libruby. You should prefer this +/// macro over [`rb_intern3`] when the string is known at compile time. +/// +/// # Safety +/// +/// This macro is safe under two conditions: +/// - Ruby VM is initialized and that thus safe to call into libruby +/// - The first call to this macro will be done inside of a managed Ruby thread (i.e. not a native thread) +/// +/// # Example +/// +/// ```no_run +/// use rb_sys::{symbol::rb_intern, rb_funcall, rb_utf8_str_new}; +/// +/// unsafe { +/// let reverse_id = rb_intern!("reverse"); +/// let msg = rb_utf8_str_new("nice one".as_ptr() as *mut _, 4); +/// rb_funcall(msg, reverse_id, 0); +/// } +/// ``` +#[macro_export] +macro_rules! rb_intern { + ($s:literal) => {{ + static mut ID: $crate::ID = 0; + if ID == 0 { + ID = $crate::rb_intern3($s.as_ptr() as _, $s.len() as _, $crate::rb_utf8_encoding()); + } + ID + }}; +}