Skip to content

Commit c4051c0

Browse files
authored
Merge pull request #1316 from godot-rust/qol/remove-static-stringname
`StringName`: remove `From` impl for `&'static CStr`
2 parents 2b09b62 + 0e3b149 commit c4051c0

File tree

9 files changed

+62
-73
lines changed

9 files changed

+62
-73
lines changed

godot-codegen/src/util.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ pub fn c_str(string: &str) -> Literal {
4141
pub fn make_string_name(identifier: &str) -> TokenStream {
4242
let lit = c_str(identifier);
4343

44-
quote! { StringName::from(#lit) }
44+
quote! { StringName::__cstr(#lit) }
4545
}
4646

4747
pub fn make_sname_ptr(identifier: &str) -> TokenStream {

godot-core/src/builtin/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ pub mod __prelude_reexport {
4141
pub use rect2i::*;
4242
pub use rid::*;
4343
pub use signal::*;
44-
pub use string::{static_name, Encoding, GString, NodePath, StringName};
44+
pub use string::{Encoding, GString, NodePath, StringName};
4545
pub use transform2d::*;
4646
pub use transform3d::*;
4747
pub use variant::*;
@@ -54,6 +54,9 @@ pub mod __prelude_reexport {
5454
#[allow(deprecated)]
5555
#[rustfmt::skip] // Do not reorder.
5656
pub use crate::dict;
57+
58+
#[cfg(feature = "trace")] // Test only.
59+
pub use crate::static_sname;
5760
}
5861

5962
pub use __prelude_reexport::*;

godot-core/src/builtin/string/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ pub use string_name::*;
2020
use crate::meta;
2121
use crate::meta::error::ConvertError;
2222
use crate::meta::{FromGodot, GodotConvert, ToGodot};
23-
pub use crate::static_name;
2423

2524
impl GodotConvert for &str {
2625
type Via = GString;

godot-core/src/builtin/string/string_name.rs

Lines changed: 48 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
* License, v. 2.0. If a copy of the MPL was not distributed with this
55
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
66
*/
7+
78
use std::fmt;
89

910
use godot_ffi as sys;
10-
use godot_ffi::interface_fn;
1111
use sys::{ffi_methods, ExtVariantType, GodotFfi};
1212

1313
use crate::builtin::{inner, Encoding, GString, NodePath, Variant};
@@ -33,11 +33,6 @@ use crate::{impl_shared_string_api, meta};
3333
/// Note that Godot ignores any bytes after a null-byte. This means that for instance `"hello, world!"` and \
3434
/// `"hello, world!\0 ignored by Godot"` will be treated as the same string if converted to a `StringName`.
3535
///
36-
/// # Performance
37-
///
38-
/// The fastest way to create string names is using [`static_name!`][crate::builtin::static_name], which creates a static cached `StringName` from null-terminated C-string literals such as `c"MyClass"`. These can be used directly by Godot without conversion. The encoding is limited to Latin-1, however. See the corresponding
39-
/// [`From<&CStr>` impl](#impl-From<%26CStr>-for-StringName).
40-
///
4136
/// # All string types
4237
///
4338
/// | Intended use case | String type |
@@ -88,7 +83,7 @@ impl StringName {
8883
let is_static = sys::conv::SYS_FALSE;
8984
let s = unsafe {
9085
Self::new_with_string_uninit(|string_ptr| {
91-
let ctor = interface_fn!(string_name_new_with_latin1_chars);
86+
let ctor = sys::interface_fn!(string_name_new_with_latin1_chars);
9287
ctor(
9388
string_ptr,
9489
cstr.as_ptr() as *const std::ffi::c_char,
@@ -248,6 +243,48 @@ impl StringName {
248243
pub fn as_inner(&self) -> inner::InnerStringName<'_> {
249244
inner::InnerStringName::from_outer(self)
250245
}
246+
247+
#[doc(hidden)] // Private for now. Needs API discussion, also regarding overlap with try_from_cstr().
248+
pub fn __cstr(c_str: &'static std::ffi::CStr) -> Self {
249+
// This used to be set to true, but `p_is_static` parameter in Godot should only be enabled if the result is indeed stored
250+
// in a static. See discussion in https://github.com/godot-rust/gdext/pull/1316. We may unify this into a regular constructor,
251+
// or provide a dedicated StringName cache (similar to ClassId cache) in the future, which would be freed on shutdown.
252+
let is_static = false;
253+
254+
Self::__cstr_with_static(c_str, is_static)
255+
}
256+
257+
/// Creates a `StringName` from a static ASCII/Latin-1 `c"string"`.
258+
///
259+
/// If `is_static` is true, avoids unnecessary copies and allocations and directly uses the backing buffer. However, this must
260+
/// be stored in an actual `static` to not cause leaks/error messages with Godot. For literals, use `is_static=false`.
261+
///
262+
/// Note that while Latin-1 encoding is the most common encoding for c-strings, it isn't a requirement. So if your c-string
263+
/// uses a different encoding (e.g. UTF-8), it is possible that some characters will not show up as expected.
264+
///
265+
/// # Safety
266+
/// `c_str` must be a static c-string that remains valid for the entire program duration.
267+
///
268+
/// # Example
269+
/// ```no_run
270+
/// use godot::builtin::StringName;
271+
///
272+
/// // '±' is a Latin-1 character with codepoint 0xB1. Note that this is not UTF-8, where it would need two bytes.
273+
/// let sname = StringName::__cstr(c"\xb1 Latin-1 string");
274+
/// ```
275+
#[doc(hidden)] // Private for now. Needs API discussion, also regarding overlap with try_from_cstr().
276+
pub fn __cstr_with_static(c_str: &'static std::ffi::CStr, is_static: bool) -> Self {
277+
// SAFETY: c_str is nul-terminated and remains valid for entire program duration.
278+
unsafe {
279+
Self::new_with_string_uninit(|ptr| {
280+
sys::interface_fn!(string_name_new_with_latin1_chars)(
281+
ptr,
282+
c_str.as_ptr(),
283+
sys::conv::bool_to_sys(is_static),
284+
)
285+
})
286+
}
287+
}
251288
}
252289

253290
// SAFETY:
@@ -354,35 +391,6 @@ impl From<&NodePath> for StringName {
354391
}
355392
}
356393

357-
impl From<&std::ffi::CStr> for StringName {
358-
/// Creates a `StringName` from a ASCII/Latin-1 `c"string"`.
359-
///
360-
/// This avoids unnecessary copies and allocations and directly uses the backing buffer. Useful for literals.
361-
///
362-
/// Note that while Latin-1 encoding is the most common encoding for c-strings, it isn't a requirement. So if your c-string
363-
/// uses a different encoding (e.g. UTF-8), it is possible that some characters will not show up as expected.
364-
///
365-
/// # Example
366-
/// ```no_run
367-
/// use godot::builtin::StringName;
368-
///
369-
/// // '±' is a Latin-1 character with codepoint 0xB1. Note that this is not UTF-8, where it would need two bytes.
370-
/// let sname = StringName::from(c"\xb1 Latin-1 string");
371-
/// ```
372-
fn from(c_str: &std::ffi::CStr) -> Self {
373-
// SAFETY: c_str is nul-terminated and remains valid for entire program duration.
374-
unsafe {
375-
Self::new_with_string_uninit(|ptr| {
376-
sys::interface_fn!(string_name_new_with_latin1_chars)(
377-
ptr,
378-
c_str.as_ptr(),
379-
sys::conv::SYS_FALSE, // p_is_static
380-
)
381-
})
382-
}
383-
}
384-
}
385-
386394
// ----------------------------------------------------------------------------------------------------------------------------------------------
387395
// Ordering
388396

@@ -489,29 +497,18 @@ mod serialize {
489497
}
490498
}
491499

500+
// TODO(v0.4.x): consider re-exposing in public API. Open questions: thread-safety, performance, memory leaks, global overhead.
501+
// Possibly in a more general StringName cache, similar to ClassId. See https://github.com/godot-rust/gdext/pull/1316.
492502
/// Creates and gets a reference to a static `StringName` from a ASCII/Latin-1 `c"string"`.
493503
///
494504
/// This is the fastest way to create a StringName repeatedly, with the result being cached and never released, like `SNAME` in Godot source code. Suitable for scenarios where high performance is required.
495505
#[macro_export]
496-
macro_rules! static_name {
506+
macro_rules! static_sname {
497507
($str:literal) => {{
498508
use std::sync::OnceLock;
499509

500-
use godot::sys;
501-
502510
let c_str: &'static std::ffi::CStr = $str;
503511
static SNAME: OnceLock<StringName> = OnceLock::new();
504-
SNAME.get_or_init(|| {
505-
// SAFETY: c_str is nul-terminated and remains valid for entire program duration.
506-
unsafe {
507-
StringName::new_with_string_uninit(|ptr| {
508-
sys::interface_fn!(string_name_new_with_latin1_chars)(
509-
ptr,
510-
c_str.as_ptr(),
511-
sys::conv::SYS_TRUE, // p_is_static
512-
)
513-
})
514-
}
515-
})
512+
SNAME.get_or_init(|| StringName::__cstr_with_static(c_str, true))
516513
}};
517514
}

godot-core/src/meta/args/as_arg.rs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
66
*/
77

8-
use std::ffi::CStr;
9-
108
use crate::builtin::{GString, NodePath, StringName, Variant};
119
use crate::meta::sealed::Sealed;
1210
use crate::meta::traits::{GodotFfiVariant, GodotNullableFfi};
@@ -562,12 +560,6 @@ impl AsArg<StringName> for &String {
562560
}
563561
}
564562

565-
impl AsArg<StringName> for &'static CStr {
566-
fn into_arg<'arg>(self) -> CowArg<'arg, StringName> {
567-
CowArg::Owned(StringName::from(self))
568-
}
569-
}
570-
571563
// ----------------------------------------------------------------------------------------------------------------------------------------------
572564
// NodePath
573565

godot-core/src/meta/class_id.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ impl ClassIdSource {
263263
fn to_string_name(&self) -> StringName {
264264
match self {
265265
ClassIdSource::Owned(s) => StringName::from(s),
266-
ClassIdSource::Borrowed(cstr) => StringName::from(*cstr),
266+
ClassIdSource::Borrowed(cstr) => StringName::__cstr(cstr),
267267
}
268268
}
269269

godot-macros/src/class/data_models/inherent_impl.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ fn add_virtual_script_call(
459459

460460
let code = quote! {
461461
let object_ptr = #object_ptr;
462-
let method_sname = ::godot::builtin::StringName::from(#method_name_cstr);
462+
let method_sname = ::godot::builtin::StringName::__cstr(#method_name_cstr);
463463
let method_sname_ptr = method_sname.string_sys();
464464
let has_virtual_override = unsafe { ::godot::private::has_virtual_script_method(object_ptr, method_sname_ptr) };
465465

itest/rust/src/builtin_tests/convert_test.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,6 @@ fn strings_as_arg() {
344344
// Note: CowArg is an internal type.
345345

346346
let str = "GodotRocks";
347-
let cstr = c"GodotRocks";
348347
let gstring = GString::from("GodotRocks");
349348
let sname = StringName::from("GodotRocks");
350349
let npath = NodePath::from("GodotRocks");
@@ -355,7 +354,6 @@ fn strings_as_arg() {
355354
assert_eq!(as_gstr_arg(npath.arg()), CowArg::Owned(gstring.clone()));
356355

357356
assert_eq!(as_sname_arg(str), CowArg::Owned(sname.clone()));
358-
assert_eq!(as_sname_arg(cstr), CowArg::Owned(sname.clone()));
359357
assert_eq!(as_sname_arg(&sname), CowArg::Borrowed(&sname));
360358
assert_eq!(as_sname_arg(gstring.arg()), CowArg::Owned(sname.clone()));
361359
assert_eq!(as_sname_arg(npath.arg()), CowArg::Owned(sname.clone()));

itest/rust/src/builtin_tests/string/string_name_test.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
use std::collections::HashSet;
99

10-
use godot::builtin::{static_name, Encoding, GString, NodePath, StringName};
10+
use godot::builtin::{static_sname, Encoding, GString, NodePath, StringName};
1111

1212
use crate::framework::{assert_eq_self, itest};
1313

@@ -127,32 +127,32 @@ fn string_name_from_cstr() {
127127
];
128128

129129
for (bytes, string) in cases.into_iter() {
130-
let a = StringName::from(bytes);
130+
let a = StringName::__cstr(bytes);
131131
let b = StringName::from(string);
132132

133133
assert_eq!(a, b);
134134
}
135135
}
136136

137137
#[itest]
138-
fn string_name_static_name() {
139-
let a = static_name!(c"pure ASCII\t[~]").clone();
138+
fn string_name_static_sname() {
139+
let a = static_sname!(c"pure ASCII\t[~]").clone();
140140
let b = StringName::from("pure ASCII\t[~]");
141141

142142
assert_eq!(a, b);
143143

144144
let a1 = a.clone();
145-
let a2 = static_name!(c"pure ASCII\t[~]").clone();
145+
let a2 = static_sname!(c"pure ASCII\t[~]").clone();
146146

147147
assert_eq!(a, a1);
148148
assert_eq!(a1, a2);
149149

150-
let a = static_name!(c"\xB1").clone();
150+
let a = static_sname!(c"\xB1").clone();
151151
let b = StringName::from("±");
152152

153153
assert_eq!(a, b);
154154

155-
let a = static_name!(c"Latin-1 \xA3 \xB1 text \xBE").clone();
155+
let a = static_sname!(c"Latin-1 \xA3 \xB1 text \xBE").clone();
156156
let b = StringName::from("Latin-1 £ ± text ¾");
157157

158158
assert_eq!(a, b);

0 commit comments

Comments
 (0)