This is a single-file C99 port of the official Rust symbol demangler (rustc-demangle
, a Rust library).
This C port is intended for situations in which a Rust dependency is hard to justify, or effectively impossible (e.g. platform toolchains, that would be used while building Rust, not the other way around).
If a Rust dependency is acceptable, using rustc-demangle
from C
(or other languages via FFI) is possible, and may be preferred over this C port.
As this C port was originally (see the History section) only for the
rustc-demangle
code demangling the (new at the time) Rust RFC2603 (aka "v0
") mangling scheme,
it may lag behind rustc-demangle
in functionality, for now.
The current port status by category is:
- ported
legacy
(pre-RFC2603 Rust symbols) demangling v0
(RFC2603 Rust symbols) demangling- ported PRs:
- [#23] Support demangling the new Rust mangling scheme (v0).
- [#26] v0: allow identifiers to start with a digit.
- [#53] v0: replace
skip_*
methods withprint_*
methods in a "skip printing" mode.- arguably backported to Rust, as the C port always took this approach
- symbol prefix flexibility (
__R
andR
, instead of_R
) - [#39] Add support for
min_const_generics
constants - [#55] v0: demangle structural constants and &str.
(only usable in
const
generics on unstable Rust)
- (UNPORTED) recursion limits
- ported PRs:
- miscellaneous
- ported PRs:
- (UNPORTED) output size limits
Notable differences (intentionally) introduced by porting:
rustc-demangle
can't use the heap (as it's#![no_std]
), but the C port does- this is mainly dictated by the ergonomics of the
rust_demangle
API, which requiresmalloc
/realloc
to return a new C string allocation - if there is demand for it,
rust_demangle
support could be made optional, forcing heap-less users to always userust_demangle_with_callback
instead - a subtler consequence is that
rustc-demangle
uses a fixed-size buffer on the stack for punycode decoding, while the C port can allocate it on the heap
- this is mainly dictated by the ergonomics of the
- Unicode support is always handrolled in the C port, and often simplified
Get rust-demangle.c
and rust-demangle.h
(via git submodule
, vendoring, etc.),
add them to your project's build system (as C source, and include path, respectively),
then you can call rust_demangle
with a symbol and some flags, e.g.:
#include <rust-demangle.h>
#include <stdio.h>
int main() {
const char *sym = "_RNvNtCsbmNqQUJIY6D_4core3foo3bar";
printf("demangle(%s) = %s\n", sym, rust_demangle(sym, 0));
printf(
"demangle(%s, VERBOSE) = %s\n", sym,
rust_demangle(sym, RUST_DEMANGLE_FLAG_VERBOSE)
);
}
which prints out, when ran:
demangle(_RNvNtCsbmNqQUJIY6D_4core3foo3bar) = core::foo::bar
demangle(_RNvNtCsbmNqQUJIY6D_4core3foo3bar, VERBOSE) = core[846817f741e54dfd]::foo::bar
Note that the example leaks the returned C strings, ideally you would free
them.
If you want to avoid the cost of allocating the output in memory, you can also
use rust_demangle_with_callback
, which takes a "printing" callback instead, e.g.:
#include <rust-demangle.h>
#include <stdio.h>
static void fwrite_callback(const char *data, size_t len, void *opaque) {
fwrite(data, len, 1, (FILE *)opaque);
}
int main() {
const char *sym = "_RNvNtCsbmNqQUJIY6D_4core3foo3bar";
printf("demangle(%s) = ", sym);
rust_demangle_with_callback(sym, 0, fwrite_callback, stdout);
printf("\n");
printf("demangle(%s, VERBOSE) = ", sym);
rust_demangle_with_callback(
sym, RUST_DEMANGLE_FLAG_VERBOSE, fwrite_callback, stdout
);
printf("\n");
}
(with identical output to the simpler example)
cargo test
will run built-in tests - it's implemented in Rust (in test-harness
)
so that it can depend on rustc-demangle
itself for comparisons.
Additionally, cargo run -q --release --example check-csv-dataset path/to/syms/*.csv
can be used to provide CSV files with additional mangled symbols test data, but such
datasets aren't trivial to obtain (existing ones required building rust-lang/rust
with a compiler patch that reacts to a custom environment variable).
They're also quite large (~1GiB uncompressed) so none have been published anywhere yet.
This C port was started while the Rust RFC2603 (aka "v0
") mangling scheme
was still being developed, with the intent of upstreaming it into libiberty
(which provides demangling for binutils
, gdb
, Linux perf
, etc.) and other
projects (e.g. valgrind
) - you can see some of that upstreaming history
on the v0
tracking issue.
At the time, the expectation was that most projects could either depend on
libiberty
, or vendor a copy of its code, so the C port focused on upstreaming
to it, rather than producing an independent reusable C codebase.
That meant that instead of a git
repository, the code revisions were only tracked by a gist,
and the GNU code style was followed (including C89 comments and variable declarations).
However, the LGPL license of libiberty
turned out to be a problem for adoption,
compared to the typical MIT/Apache-2.0 dual licensing of Rust projects.
This repository started out as a fork of the original gist, at commit e2c30407516a87c0f8c3820cf152640bd08805dd
, just before libiberty
integration (which was in commit 0e6f57b0e86ccec4395f8850f4885b1e391a9f4b
).
Any changes since that gist are either fresh C ports of the Rust rustc-demangle
code, or completely new code, in order to maintain the MIT/Apache-2.0 dual licensing.
While this has the disadvantage of starting behind libiberty
(which kept its
Rust legacy
demangler, and also got a few more features during/since upstreaming),
the relationship may reverse eventually, where this port could get new features
that would then have to be upstreamed into libiberty
.
Like rustc-demangle
, this project is licensed under either of
- Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in rust-demangle.c
you, as defined in the Apache-2.0 license, shall
be dual licensed as above, without any additional terms or conditions.