Module varargs

Source
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());
}

Structs§

TooMany
A sentinel type for trying to append too many (>12) arguments to a VarArgs tuple.

Traits§

Arguments
Implements a list of arguments to a vararg call.
VarArgs
Represents a variable length list of arguments to printf.