#ascii #string #safe

soft-ascii-string

char/str/string wrappers which add a “is-ascii” soft constraint

6 releases (3 stable)

Uses old Rust 2015

1.1.0 Feb 1, 2020
1.0.1 Oct 11, 2019
1.0.0 Jun 11, 2018
0.2.0 Jan 9, 2018
0.1.0 Nov 7, 2017

#17 in #ascii

Download history 15/week @ 2021-01-11 27/week @ 2021-01-18 12/week @ 2021-01-25 9/week @ 2021-02-01 18/week @ 2021-02-08 31/week @ 2021-02-15 16/week @ 2021-02-22 14/week @ 2021-03-01 11/week @ 2021-03-08 9/week @ 2021-03-15 26/week @ 2021-03-22 26/week @ 2021-03-29 19/week @ 2021-04-05 21/week @ 2021-04-12 30/week @ 2021-04-19 13/week @ 2021-04-26

67 downloads per month
Used in 8 crates (5 directly)

MIT/Apache

51KB
1.5K SLoC

soft-ascii-string Crates.io soft-ascii-string License License

soft-ascii-string provides char, str and string wrapper which add an "is-ascii" soft constraint.

As it is a soft constraint it can be violated, while a violation is (normally) a bug it does not introduce any safety issues. In this soft-ascii-string differs to e.g. ascii which uses a hard constraint and where a violation does brake rust safety and potentially introduces undefined behavior.

Soft-ascii-string is suited for situations where many places (e.g. external libraries) output strings which should be ascii and which you do not want to iterate over to assure they are ascii but where you neither want to use a unsafe conversions as it would be required by the ascii crate.

This crate is not necessarily suited if you want to rally on the string being ascii on a safety level, you might want to consider using ascii in that case.

Documentation can be viewed on docs.rs.

Example

extern crate soft_ascii_string;

use soft_ascii_string::{SoftAsciiChar, SoftAsciiStr, SoftAsciiString};

fn main() {
    // encoder_stub should encode all non-ascii chars
    // but it's a complex external dependency so we do
    // not want to rely on it on a safety level
    let mut ascii = SoftAsciiString::from_unchecked(external::encoder_stub("magic↓"));

    // we know ":" is ascii so no unnecessary checks here
    ascii.push(SoftAsciiChar::from_unchecked(':'));
    // we know "abcde" is ascii so no unnecessary checks here
    ascii.push_str(SoftAsciiStr::from_unchecked("abcde"));

    // lets assume we got this from somewhere
    let other_input = "other string";
    let part = SoftAsciiStr::from_str(other_input)
        .expect("other_input should have been ascii");
    ascii.push_str(part);

    let mut clone = SoftAsciiString::with_capacity(ascii.len());
    // the chars(), char_indices() operators return a
    // iterator returning SoftAsciiChars
    for ch in ascii.chars() {
        clone.push(ch);
    }

    // we do all kind of cost transformations
    // without having to revalidate that it is
    // ascii as long as we do not want to rely on it
    internal::costy_transformations(&mut ascii);

    // when running unsafe code we really do not want a bug
    // which introduced non ascii code to introduce unsafety
    // so we can just validate if it really is ascii.
    // On the other hand as long as we do not need a 100% guarantee
    // for security reason we do not need to call revalidate.
    match ascii.revalidate_soft_constraint() {
        Ok(ascii) => {
            unsafe {external::requires_ascii(ascii.as_bytes())}
        },
        Err(err) => panic!("non-ascii content in ascii string")
    }

}


mod internal {
    use soft_ascii_string::SoftAsciiString;

    pub fn costy_transformations(s: &mut SoftAsciiString) {
        let s2 = s.clone();
        s.insert_str(0, &*s2)
    }
}

mod external {

    // lets assume this is an external function only working with ascii
    pub unsafe fn requires_ascii(b: &[u8])  {}

    // lets assume this is more complex and
    // from a external dependency, we assume
    // it returns ascii, but what if there is
    // a bug
    pub fn encoder_stub(s: &str) -> String {
        let mut out = String::with_capacity(s.len());
        for ch in s.chars() {
            if ' ' <= ch && ch <= '~' {
                out.push(ch)
            } else { out.push('?') }
        }
        out
    }

}

Error handling:

extern crate soft_ascii_string;

use soft_ascii_string::{SoftAsciiChar, SoftAsciiStr, SoftAsciiString};

fn main() {
    let non_ascii_input: String = "←↓↓↓".into();
    match SoftAsciiString::from_string(non_ascii_input) {
        Ok(ok_value) => panic!("the string should not have been ascii"),
        Err(err) => {
            let original_source: String = err.into_source();
            println!("the input was: {:?}", original_source)
        }
    }
}

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Change Log

  • v1.0
    • added from_unchecked to SoftAsciiChar, SoftAsciiStr, SoftAsciiString
    • deprecated from_char_unchecked, from_str_unchecked, from_string_unchecked

No runtime deps