Expand description
§Infrastructure for building calls to printf
.
The varargs
modules solves a particularly tricky problem: Some arguments
passed to pw_log
result in a single argument passed to printf (a u32
is passed directly) while some may result in multiple arguments (a &str
is passed as a length argument and a data argument). Since function like
proc macros don’t know the types of the arguments we rely on chained generic
types.
§VarArgs trait
The VarArgs
both encapsulates the structure of the arguments as well
as provides a method for calling printf
. It accomplishes this through a
recursive, chained/wrapped type through it’s OneMore<T>
associated type.
We then provide generic implementations for VarArgs
for tuples
ov values that implement Clone
such that:
type VarArgsType =
<<<<() as VarArgs>
::OneMore<u32> as VarArgs>
::OneMore<i32> as VarArgs>
::OneMore<c_int> as VarArgs>
::OneMore<*const c_uchar>;
type TupleType = (u32, i32, c_int, *const c_uchar);
assert_eq!(TypeId::of::<VarArgsType>(), TypeId::of::<TupleType>());
VarArgs
provides an append()
method that allows building up these
typed tuple values:
let string = "test";
let args = ()
.append(0u32)
.append(-1i32)
.append(string.len() as c_int)
.append(string.as_ptr().cast::<*const c_uchar>());
assert_eq!(args, (
0u32,
-1i32,
string.len() as c_int,
string.as_ptr().cast::<*const c_uchar>()));
Lastly VarArgs
exposes an unsafe call_printf()
method that calls
printf and passes the build up arguments to printf()
:
let string = "test";
unsafe {
// This generates a call to:
// `printf("[%s] %u %d %.*s\0".as_ptr(), "INF".as_ptr(), 0u32, -1i32, 4, string.as_ptr())`.
()
.append(0u32)
.append(-1i32)
.append(string.len() as c_int)
.append(string.as_ptr().cast::<*const c_uchar>())
// Note that we always pass the null terminated log level string here
// as an argument to `call_printf()`.
.call_printf("[%s] %u %d %.*s\0".as_ptr().cast(), "INF\0".as_ptr().cast());
}
§Arguments Trait
The Arguments
trait is the final piece of the puzzle. It is used to
generate one or more calls to VarArgs::append()
for each argument to
pw_log
. Most simple cases that push a single argument look like:
impl Arguments<i32> for i32 {
type PushArg<Head: VarArgs> = Head::OneMore<i32>;
fn push_arg<Head: VarArgs>(head: Head, arg: &i32) -> Self::PushArg<Head> {
head.append(*arg)
}
}
A more complex case like &str
can push multiple arguments:
impl Arguments<&str> for &str {
// Arguments are a chain of two `OneMore` types. One for the string length
// and one for the pointer to the string data.
type PushArg<Head: VarArgs> =
<Head::OneMore<c_int> as VarArgs>::OneMore<*const c_uchar>;
// `push_arg` pushes both the length and pointer to the string into the args tuple.
fn push_arg<Head: VarArgs>(head: Head, arg: &&str) -> Self::PushArg<Head> {
let arg = *arg;
head.append(arg.len() as c_int).append(arg.as_ptr().cast::<*const c_uchar>())
}
}
§Putting it all together
With all of these building blocks, the backend proc macro emits the following code:
// Code emitted for a call to `info!("Hello {}. It is {}:00", "Pigweed" as &str, 2 as u32)
let args = ();
let args = <&str as Arguments<&str>>::push_arg(args, &("Pigweed" as &str));
let args = <u32 as Arguments<u32>>::push_arg(args, &(2 as u32));
unsafe {
args.call_printf("[%s] Hello %.*s. It is %d:00".as_ptr().cast(), "INF\0".as_ptr().cast());
}