Is it possible to access a struct's private field outside the module?

A structure in the standard library meets my needs, but its fields are private, and the standard library does not provide an appropriate way to initialize it.

I only need a small code snippet from the standard library. How can I avoid including a copy of the entire standard library in my code library?

As a concession, I don't care about the safety of the code.

Can anyone help me, thanks!

No, there's no way to access private fields from another crate, even with unsafe code.

Which std type are you working with and why do you need to access its private fields? We might be able to help you figure out an alternative approach using the existing API, or you could open an issue asking for the interface you need to be exposed.

3 Likes

@skanfd, You're in luck mate - https://twitter.com/CecileTonglet/status/1429020136535695363

5 Likes

Thanks for your reply!

I want to implement an extended Rust style escape sequence encoder. (add more escape sequence)

The structure I need to access is core::char::{EscapeDebug, EscapeDefaultState}, which is an automaton and I want to give it a specific state.

Is it possible? Thank you!

    /// An extended version of `escape_debug` that optionally permits escaping
    /// Extended Grapheme codepoints, single quotes, and double quotes. This
    /// allows us to format characters like nonspacing marks better when they're
    /// at the start of a string, and allows escaping single quotes in
    /// characters, and double quotes in strings.
    #[inline]
    pub(crate) fn escape_debug_ext(self, args: EscapeDebugExtArgs) -> EscapeDebug {
        let init_state = match self {
            '\t' => EscapeDefaultState::Backslash('t'),
            '\r' => EscapeDefaultState::Backslash('r'),
            '\n' => EscapeDefaultState::Backslash('n'),
            '\\' => EscapeDefaultState::Backslash(self),
            '"' if args.escape_double_quote => EscapeDefaultState::Backslash(self),
            '\'' if args.escape_single_quote => EscapeDefaultState::Backslash(self),
            _ if args.escape_grapheme_extended && self.is_grapheme_extended() => {
                EscapeDefaultState::Unicode(self.escape_unicode())
            }
            _ if is_printable(self) => EscapeDefaultState::Char(self),
            _ => EscapeDefaultState::Unicode(self.escape_unicode()),
        };
        EscapeDebug(EscapeDefault { state: init_state })
    }

It might be '?' => EscapeDefaultState::Backslash('?') or something else.

Magic! :yum:
But I can't implement Deseriable for std structs, I guess ...

if you mean serde::Deserialize, then you can create a newtype wrapper and implement the trait for that.

1 Like

This won't let you use the referenced trick to access the inner type's private fields, though.

1 Like

Indeed, but I thought that's an orthogonal issue. Private fields aren't accessible without the newtype wrapper, either.

1 Like

Serialize/Deserialize came up because of this link posted above, explaining how they can be abused to access otherwise private fields in Rust types.

3 Likes

Apoligies all/both, if what I had shared was misleading..

1 Like

Thanks for all replies! I appreciate for all efforts to help!
I decide to duplicate the code snippets from std to my code, tedious but works :blush:

1 Like

You could transmute it to a struct with the same layout, or use pointer arithmetic. But that is very unsafe. At the very least it could break if the std implementation ever changed, and could potentially have undefined behavior.

1 Like

Unless the struct has some specific #[repr] on it, there's no struct with the same layout, even you copy-paste the std's declaration. It is explicitly stated that there are no guarantees of data layout made, so compiler can freely reorder its fields based on the declaration position, usage pattern, or even pseudo-random to prevent people to rely on unguaranteed implementation detail.

15 Likes

It could be a solution, in such case that I can transmute something first to gain inter-operation, and then contribute code to other crate.

Shocking! You mean the compiler will generate random fields order on purpose? Will the compiler generate different binary for the same code?

There is discussion about adding a flag to have it do this to help catch incorrect transmutes, but it wont be enabled by default.

2 Likes

Reproducible builds are also a goal, so a different binary for the same code with the same compiler version (and same flags and same PGO input and and and...) is probably a bug. (But there's still no guarantee that two repr(Rust) structs with the same fields have the same layout within the same compilation, say.)

1 Like

I don't see how reproducible builds are incompatible with randomized layout.
Just take source, call SHA512 on it and use that to seed random-number generator. Plus "salt" command-line option to make sure different builders would build different binaries.
And yes, it would be nice to have it enabled by default.
Not only this would make sure people wouldn't try dirty tricks, but, more importantly, that's quite nice security measure.
Linux kernel does that despite using C, thus it sound logical that Rust, being concerned about security have to do that, too.
Although I'm not sure how high this should prioritized. There are lots of other things which are more important.

5 Likes

Some ISAs encode instructions more compactly when field offsets are small (i.e., 0, 1..3F, etc). An optimizing compiler could make a static or PTO-based assessment of which fields are accessed more frequently and order them in the struct in a way that minimizes i-cache fetches. In such a case it is completely reasonable for the compiler to lay out differently two unrelated structs in the same program that just happen to have the same nominal alignment/size ordering of fields.

8 Likes

Hence my statement that it could be undefined behavior. You might be able to get it to work with a specific version of rust, but you can't rely on it.

2 Likes