Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a starts_with(s: &str) -> bool method for fmt::Arguments? #66773

Open
phil-opp opened this issue Nov 26, 2019 · 3 comments
Open

Add a starts_with(s: &str) -> bool method for fmt::Arguments? #66773

phil-opp opened this issue Nov 26, 2019 · 3 comments
Labels
C-feature-request Category: A feature request, i.e: not implemented / a PR. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.

Comments

@phil-opp
Copy link
Contributor

It's currently very cumbersome to check whether a panic message starts with an expected &str in no_std mode:

#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
    use core::fmt::Write;

    let mut check_message = CheckPanicMessage::new("some panic message:");
    let error = write!(&mut check_message, "{}", info.message().unwrap_or(&format_args!(""))).is_err();
    if !error && check_message.starts_as_expected() {
       // do something (e.g. mark test as successful)
    } else {
        // do something else (e.g. mark test as failed)
    }
}

struct CheckPanicMessage<'a> {
    expected_prefix: &'a str,
    mismatch: bool,
}

impl<'a> CheckPanicMessage<'a> {
    fn new(expected_prefix: &'a str) -> Self {
        Self {
            expected_prefix,
            mismatch: false,
        }
    }

    fn starts_as_expected(&self) -> bool {
        !self.mismatch && self.expected_prefix == ""
    }
}

use core::fmt;

impl fmt::Write for CheckPanicMessage<'_> {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        let len = s.len().min(self.expected_prefix.len());
        if !self.mismatch {
            if s.starts_with(&self.expected_prefix[..len]) {
                serial_println!("[ok] expected: <{}>, got: <{}>", &self.expected_prefix[..len], s);
                self.expected_prefix = &self.expected_prefix[len..];
            } else {
                serial_println!("expected: <{}>, got: <{}>", &self.expected_prefix[..len], s);
                self.mismatch = true;
            }
        }
        Ok(())
    }
}

(If there is an easier way to achieve this, please let me know.)

How about adding a starts_with(&self, &str) -> bool method to fmt::Arguments? With such a method, the above could be shortened to:

#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
    if info.message().unwrap_or(&format_args!("")).starts_with("some panic message:") {
        // do something (e.g. mark test as successful)
    } else {
        // do something else (e.g. mark test as failed)
    }
}

I would be happy to write a PR that adds this as an unstable method if it is desired.

@hellow554
Copy link
Contributor

Do you think a starts_with is better than a as_str method? I see your point here, but adding such a niche function? as_str would be more generic and you can do other things as well (e.g. ends_with).

What do you think?

@phil-opp
Copy link
Contributor Author

phil-opp commented Nov 26, 2019

@hellow554 The problem is that fmt::Arguments can consist of many individual components. For example, when you do format_args!("one {} three", "two") the fmt::Arguments type contains the three individual substrings "one", "two", and "three". Returning a &str is not possible without allocating new storage for joining the three substrings first. This is the reason that the format macro performs heap allocations behind the scenes, which in turn means that it is not available in no_std environments.

@jonas-schievink jonas-schievink added C-feature-request Category: A feature request, i.e: not implemented / a PR. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. labels Nov 26, 2019
@apogeeoak
Copy link

apogeeoak commented May 1, 2021

A starts_with method on fmt::Arguments would be nice. In the meantime your code can be shortened, not down to a single line but ever so slightly.

pub fn panic_with(info: &PanicInfo, message: &str) -> ! {
    if CheckPanicMessage::starts_with(info, message) {
        // Ok
    } else {
        // Failed.
    }
}

use core::cmp;
use core::fmt;
use core::str::Bytes;

pub struct CheckPanicMessage<'a> {
    bytes: Bytes<'a>,
}

impl CheckPanicMessage<'_> {
    pub fn starts_with(info: &PanicInfo, message: &str) -> bool {
        let mut check = CheckPanicMessage { bytes: message.bytes() };
        use core::fmt::Write;
        write!(&mut check, "{}", info).is_ok() && check.bytes.len() == 0
    }
}

impl fmt::Write for CheckPanicMessage<'_> {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        let len = cmp::min(self.bytes.len(), s.len());
        self.bytes.by_ref().take(len).eq(s.bytes().take(len))
            .then(|| ()).ok_or(fmt::Error)
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-feature-request Category: A feature request, i.e: not implemented / a PR. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

4 participants