Amanieu / cstr_core

Implementation of CStr and CString for no_std environments
Apache License 2.0
41 stars 17 forks source link

Provide `cstr!()` macro #18

Closed chrysn closed 3 years ago

chrysn commented 3 years ago

The std cstr has a convenient macro that allows in-place creation of C strings, a la

fn safe_wrapper(arg1: usize, arg2: &CStr) {
    unsafe { wrapped_sys::c_function(arg1, arg2) };
}

safe_wrapper(1, cstr!("Hello"));

This should be easy to do with a macro like

#[macro_export]
macro_rules! cstr {
    ($s:expr) => {{
        let a = concat!($s, "\0");
        unsafe { $crate::CStr::from_bytes_with_nul_unchecked(a.as_bytes()) }
    }};
}

which I'm so far using in the riot-sys crate (but would prefer to migrate to something off-the-shelf).

I can shape it into a PR (but not right now), but if there's a good reason not to do this, please let me (or whoever would next pick this up faster) know.

chrysn commented 3 years ago

I stand slightly corrected: The macro is not in std but in the cstr crate -- but either way, it'd be good to have here right away.

Amanieu commented 3 years ago

It's not enough to just add a trailing zero, you must also ensure that there are no interior nul bytes in the string. So you still have to use the safe from_bytes_with_nul function.

Amanieu commented 3 years ago

I'd be happy to accept a PR to add a proc macro sub-crate to cstr_core which contains a port of the cstr! macro from the cstr crate.

chrysn commented 3 years ago

I was unaware that no-interior-nulls is a guarantee that CStr gives (the stripped down copy I'm maintaining in-tree until this is resolved and I can just switch over doesn't need them).

The proc macro appears to be a bit tricky to get to run in no_std.

The approach I'm exploring is to have a const function that'd do the checking. In first experiments, I got that down to zero-cost in the good case -- meaning that adding an assert!($crate::CStr::is_cstr(a.as_bytes())); to the macro does not make the release mode binary any larger. (The non-release mode gets shorter by a lot, as in my test example it panics early in main; making it raise at build time would be nice but can later be added when const_panic feature is stable).

The check function employed is necessarily different from the checks done in the safe conversion; until all involved is const, that makes sense. (Const function writing is a bit awkward still, but IMO still more readable than a proc macro).

Would that approach work for you? If so, I can work it into this crate and make a PR out of it.

Current check function for my later reference ```rust #[inline] pub const fn bytes_are_valid(bytes: &[u8]) -> bool{ if bytes.len() == 0 || bytes[bytes.len() - 1] != 0 { return false; } let mut index = 0; // No for loops yet in const functions while index < bytes.len() - 1 { if bytes[index] == 0 { return false; } index += 1; } true } ```
Amanieu commented 3 years ago

You can use a static_assert! like the one here to ensure the check happens at build time.

I'm happy to accept a PR for this.