2

I am making a VGA print macro for my OS with no_std, and it is for some reason not working. I am using the vga crate so that I do not have to do all the VGA code myself. I have a function called _print:

use core::fmt::{Arguments, Write};
use vga::colors::Color16;
use vga::writers::{Graphics640x480x16, GraphicsWriter};

pub fn _print(args: Arguments) {
    unsafe {
        let mut i: usize = 0;
        for (_offset, character) in args.as_str().unwrap().chars().enumerate() {
            Mode.draw_character(CurrentTextX + i, CurrentTextY, character, CurrentTextColor);
            i += 8;
        }
    }
}

I then have a macro called vprint!:

#[macro_export]
macro_rules! vprint {
    ($($arg:tt)*) => {
        crate::videographicsarray::_print(format_args!($($arg)*));
    };
}

I am not using alloc. I have seen code exactly like this work, but for some reason my code does not work. It displays text I enter, but if I pass any arguments, it panics. What am I doing wrong?

trent
  • 25,033
  • 7
  • 51
  • 90

1 Answers1

2

Your code panics because as_str() only returns Some, if there is no arguments. So when you immediately unwrap() it will panic, for instances where you have arguments to be formatted.

Get the formatted string, if it has no arguments to be formatted.

This can be used to avoid allocations in the most trivial case.

std::fmt::Arguments::as_str() docs

Instead you can use args.to_string() instead of args.as_str().unwrap(), to format it into a String. So it actually formats regardless of whether there is any arguments or not.

pub fn _print(args: Arguments) {
    unsafe {
        let mut i: usize = 0;
        for (_offset, character) in args.to_string().chars().enumerate() {
            Mode.draw_character(CurrentTextX + i, CurrentTextY, character, CurrentTextColor);
            i += 8;
        }
    }
}

If you want to avoid the needless allocations, then you could use unwrap_or_else() and only do to_string() if as_str() returns None. While wrapping in Cow to avoid converting the &str to a String anyways.

use std::borrow::Cow;

let formatted_str = args
    .as_str()
    .map(Cow::Borrowed)
    .unwrap_or_else(|| Cow::Owned(args.to_string()));

no_std

Since you're using no_std and not using the alloc crate. Then you can define a u8 buffer to format into. E.g. use let buf = [u8; N];, where N is enough space for your formatted strings.

After that, you can implement your own wrapper around a &mut [u8] buffer, which implements the core::fmt::Write trait. You can use that wrapper along with with core::fmt::write().

Instead of implementing your own, you can use the struct WriteTo from this answer: How can I use the format! macro in a no_std environment?

pub fn _print(args: Arguments) {
    let mut buf = [0u8; 64];
    let formatted_str: &str = write_to::show(&mut buf, args).unwrap();

    unsafe {
        let mut i: usize = 0;
        for (_offset, _character) in formatted_str.chars().enumerate() {
            Mode.draw_character(CurrentTextX + i, CurrentTextY, character, CurrentTextColor);

            i += 8;
        }
    }
}
vallentin
  • 23,478
  • 6
  • 59
  • 81
  • It gives an error: 'no method named `to_string` found for struct `Arguments<'_>` in the current scope' –  Mar 27 '21 at 18:32
  • @DuelTheBearded Wait, are you using `no_std`? What about `alloc`? – vallentin Mar 27 '21 at 18:36
  • I am using `no_std`, yeah. –  Mar 27 '21 at 18:39
  • @DuelTheBearded Could you update the question, to include your `no_std` "setup". Like which crates you're using, e.g. `core` and `alloc`? – vallentin Mar 27 '21 at 18:42
  • @DuelTheBearded Didn't make it completely clear. I'd need to know if you're using `alloc`. If not, then you'd need to use something else to write to, and then instead use e.g. [`core::fmt::write(&mut writer, args)`](https://doc.rust-lang.org/core/fmt/fn.write.html). All in all `as_str()` doesn't work, because it only returns `Some` if there are no arguments to be formatted. – vallentin Mar 27 '21 at 18:49
  • @DuelTheBearded No problem, happy I could help. But in the future, probably highlight you're using `no_std`, my eyes glossed over the mention of OS :) – vallentin Mar 27 '21 at 19:05