Style guide for platform specific code
Code organization
The crosvm code can heavily interleave platform specific code into platform agnostic code using
#[cfg(target_os = "")]
. This is difficult to maintain as
- It reduces readability.
- Difficult to write/maintain unit tests.
- Difficult to maintain downstream, proprietary code
To address the above mentioned issue, the style guide provides a way to standardize platform specific code layout.
Consider a following example where we have platform independent code, PrintInner
, which is used by
platform specific code, WinPrinter
and UnixPrinter
to tweak the behavior according to the
underlying platform. The users of this module, sys
, get to use an aliased struct called Printer
which exports similar interfaces on both the platforms.
In this scheme print.rs
contains platform agnostic logic, structures and traits. Different
platforms, in linux.rs
and windows.rs
, implement traits defined in print.rs
. Finally sys.rs
exports interfaces implemented by platform specific code.
In a more complex library, we may need another layer, print.rs
, that uses traits and structures
exported by platform specific code, linux/print.rs
and windows/print.rs
, and adds some more
common logic to it. Following example illustrates the scheme discussed above. Here,
Printer.print()
is supposed to print a value of u32
and print the target os name.
The files that contain platform specific code only should live in a directory named sys/
and
those files should be conditionally imported in sys.rs
file. In such a setup, the directory
structure would look like,
$ tree
.
├── print.rs
├── sys
│ ├── linux
│ │ └── print.rs
│ ├── linux.rs
│ ├── windows
│ │ └── print.rs
│ └── windows.rs
└── sys.rs
File: print.rs
File: sys/windows/print.rs
File: sys/linux/print.rs
File: sys.rs
Imports
When conditionally importing and using modules, use
cfg(any(target_os = "android", target_os = "linux"))
and cfg(windows)
for describing the
platform. Order imports such that common comes first followed by linux and windows dependencies.
Structure
It is OK to have a few platform specific fields inlined with cfgs. When inlining
- Ensure that all the fields of a particular platform are next to each other.
- Organize common fields first and then platform specific fields ordered by the target os name i.e. "linux" first and "windows" later.
If the structure has a large set of fields that are platform specific, it is more readable to split it into different platform specific structures and have their implementations separate. If necessary, consider defining a crate in platform independent and have the platform specific files implement parts of those traits.
Enum
When enums need to have platform specific variants
- Create a new platform specific enum and move all platform specific variants under the new enum
- Introduce a new variant, which takes a platform specific enum as member, to platform independent enum.
Do
File: sys/linux/base.rs
File: sys/windows/base.rs
File: base.rs
Don't
File: base.rs
Exception: dispatch enums (trait-object like enums) should NOT be split
Dispatch enums (enums which are pretending to be trait objects) should NOT be split as shown above. This is because these enums just forward method calls verbatim and don't have any meaningful cross platform code. As such, there is no benefit to splitting the enum. Here is an acceptable example:
Errors
Inlining all platform specific error values is ok. This is an exception to the enum to keep error handling simple. Organize platform independent errors first and then platform specific errors ordered by the target os name i.e. "linux" first and "windows" later.
Code blocks and functions
If a code block or a function has little platform independent code and the bulk of the code is
platform specific then carve out platform specific code into a function. If the carved out function
does most of what the original function was doing and there is no better name for the new function
then the new function can be named by appending _impl
to the functions name.
Do
File: base.rs
File: sys/linux/base.rs
File: sys/windows/base.rs
Don't
File: base.rs
match
With an exception to matching enums, see enum, matching for platform specific values can be
done in the wildcard patter(_
) arm of the match statement.
Do
File: parse.rs
File: sys/linux/parse.rs
File: sys/windows/parse.rs
Don't
File: parse.rs
Platform specific symbols
If a platform exports symbols that are specific to the platform only and are not exported by all other platforms then those symbols should be made public through a namespace that reflects the name of the platform.
File: sys.rs
File: linux.rs
File: windows.rs
The user of the library, say mylib, now has to do something like below which makes it explicit that
the functions print_u8
and print_u16
are platform specific.