Emit string and byte-string constants (#927, #546)#1171
Conversation
emilio
left a comment
There was a problem hiding this comment.
Looks sensible. One question about the const char* version of this, and thanks for the ping!
| let ty = Type::load(ty)?; | ||
| let mut ty = match ty { | ||
| Some(ty) => ty, | ||
| let (mut ty, mut lit) = match byte_string_bytes(expr) { |
There was a problem hiding this comment.
A bit hacky, but fair enough I guess?
There was a problem hiding this comment.
Yeah, it was. Pulled it into a single load_byte_string(expr) -> Option<(Type, Literal)> so it reads as "byte string, else the usual typed load." The type and value have to come out together: &[u8; N] loads as a pointer-to-array rather than a bare array, and &[u8] doesn't load at all, so I synthesize the uint8_t[N] from the literal length right next to the initializer. Nice side effect is the sized and unsized forms end up identical.
|
|
||
| }; | ||
| constexpr static const int32_t Foo_GA = 10; | ||
| constexpr static const char *Foo_BU = "hello world"; |
There was a problem hiding this comment.
Is this intentional choice of char pointer intentional out of curiosity? const char Foo_BU[] = might be more useful as it allows you to get the length at compile time more easily, and it's what the actual literal would produce.
There was a problem hiding this comment.
Yeah, agreed — done. Scalar &str/&CStr now emit const char NAME[]. Compile-time sizeof is the obvious win, and it also stops a &str with an interior NUL from getting silently truncated the way the pointer form did. Arrays of them stay const char *[N] though — ragged lengths can't be char[N][]. C is unaffected, still a bare #define.
`&str` and `&CStr` constants were silently dropped; they are now emitted
as C string literals. A scalar `&str`/`&CStr` becomes `const char NAME[]`
(an unsized array) in the C++/Cython typed forms, so `sizeof` gives the
length at compile time and the declaration mirrors its initializer; in C
it stays a bare `#define`. An array of them (`[&CStr; N]`) instead maps to
`const char *[N]`, since ragged element lengths can't form a `char[N][]`.
Byte-string constants (`b"..."`) are emitted as `uint8_t[]` array
initializers rather than string literals, so non-printable and
non-NUL-terminated data survives. The declared type (`&[u8; N]` or the
unsized `&[u8]`) doesn't load into a bare array, so `load_byte_string`
derives both the type and the initializer from the literal bytes, which
also makes the sized and unsized forms behave identically. An empty byte
string yields the same zero-length array as any other empty array
constant; it is not special-cased.
`&str`/`&CStr` are fat pointers, so their C form is not ABI-compatible
with the Rust type; what is emitted is the string *value*, which is what a
C consumer of these constants wants. The rewrite is confined to constant
definitions and never applied to fields or arguments.
The C string escaper renders non-ASCII UTF-8 bytes as `\xNN` (not Rust's
`\u{...}`, which is invalid C), escapes `?` to prevent trigraph
formation, and splits a literal when a `\xNN` escape is followed by a
hex-digit character so the escape stays one byte.
The three const-dedup checks that suppress a doubled `const` for pointer
constants are consolidated into `write_field_prepends_const`, which also
recurses through arrays so arrays of const pointers dedup correctly.
c1c8c8b to
d0d6d28
Compare
Closes #927 and closes #546.
&str,&CStr, and byte-string (b"...") constants were previously dropped silently. This emits them:&strand&CStrconstants become C string literals —#define,constexpr, orstatic const char *depending on config. Arrays of them ([&CStr; N]) work too.b"...") becomeuint8_t[]array initializers rather than string literals, so non-printable and non-NUL-terminated data survives (per the guidance in Support for constant byte literal #546). The declared type may be the unsized&[u8], which cbindgen can't load, so the array length is taken from the literal. An empty byte string yields the same zero-length array as any other empty array constant — it is not special-cased.Design notes (to save reviewer time)
const char *for&str/&CStr. These are fat/thin pointers, soconst char *is not ABI-compatible with the Rust type. But a Rustconsthas no symbol or address — it is inlined at every use site — so there is nothing to link against and no ABI to preserve. What a C consumer of the constant wants is the string value, which is what we emit.&str/&CStr→const char *rewrite is confined toConstant::load, not the global type-simplification pass. Collapsing these types is only sound for a constant definition, where the runtime representation is discarded in favor of the literal; it must never apply to struct fields, function arguments, or returns.Literal::loadsees only the expression, so it can produce a self-describing string literal but cannot synthesize theuint8_t[N]type a byte string needs — that requires the length, which onlyConstant::loadreasons about. Nested byte strings remain unsupported (same silent-skip as before).\xNN(not Rust's\u{...}, which is invalid C), escapes?to prevent trigraph formation, and splits a literal when a\xNNescape is followed by a hex-digit character ("...\xNN" "f...") so the escape stays a single byte.Tests
New fixtures cover string edge cases (empty, interior NUL, non-UTF-8
&CStr, hex-digit adjacency in both cases, control/DEL bytes, trigraphs, arrays), byte strings (sized, unsized slice, binary, empty), and theallow_static_const/allow_constexproff fallback path. Several existingconstant/assoc_constant/namespace*expectations gained the&strconstants they previously dropped.