hylarana_common/
strings.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
use std::{
    ffi::{c_char, CStr, CString},
    ptr,
    str::Utf8Error,
};

use thiserror::Error;

#[derive(Debug, Error)]
pub enum StringError {
    #[error(transparent)]
    Utf8Error(#[from] Utf8Error),
    #[error("the string ptr is null")]
    Null,
}

/// A type representing an owned, C-compatible, nul-terminated string with no
/// nul bytes in the middle.
///
/// This type serves the purpose of being able to safely generate a C-compatible
/// string from a Rust byte slice or vector. An instance of this type is a
/// static guarantee that the underlying bytes contain no interior 0 bytes (“nul
/// characters”) and that the final byte is 0 (“nul terminator”).
///
/// CString is to &CStr as String is to &str: the former in each pair are owned
/// strings; the latter are borrowed references.
pub struct Strings {
    ptr: *const c_char,
    drop: bool,
}

impl Drop for Strings {
    fn drop(&mut self) {
        if self.drop && !self.ptr.is_null() {
            drop(unsafe { CString::from_raw(self.ptr as *mut c_char) })
        }
    }
}

impl From<*const c_char> for Strings {
    fn from(ptr: *const c_char) -> Self {
        Self { drop: false, ptr }
    }
}

impl From<&str> for Strings {
    fn from(value: &str) -> Self {
        Self {
            ptr: CString::new(value).unwrap().into_raw(),
            drop: true,
        }
    }
}

impl From<String> for Strings {
    fn from(value: String) -> Self {
        Self {
            ptr: CString::new(value).unwrap().into_raw(),
            drop: true,
        }
    }
}

impl Strings {
    /// Yields a &str slice if the CStr contains valid UTF-8.
    ///
    /// If the contents of the CStr are valid UTF-8 data, this function will
    /// return the corresponding &str slice. Otherwise, it will return an error
    /// with details of where UTF-8 validation failed.
    pub fn to_string(&self) -> Result<String, StringError> {
        if !self.ptr.is_null() {
            Ok(unsafe { CStr::from_ptr(self.ptr) }
                .to_str()
                .map(|s| s.to_string())?)
        } else {
            Err(StringError::Null)
        }
    }

    /// Returns the inner pointer to this C string.
    ///
    ///The returned pointer will be valid for as long as self is, and points to
    /// a contiguous region of memory terminated with a 0 byte to represent the
    /// end of the string.
    ///
    ///The type of the returned pointer is *const c_char, and whether it’s an
    /// alias for *const i8 or *const u8 is platform-specific.
    ///
    /// ### WARNING
    ///
    ///The returned pointer is read-only; writing to it (including passing it
    /// to C code that writes to it) causes undefined behavior.
    pub fn as_ptr(&self) -> *const c_char {
        self.ptr
    }
}

#[macro_export]
macro_rules! c_str {
    ($s:expr) => {
        hylarana_common::strings::Strings::from($s).as_ptr()
    };
}

pub fn write_c_str(src: &str, dst: *mut c_char) {
    let src = src.as_bytes();
    let len = src.len();

    unsafe {
        ptr::copy(src.as_ptr().cast(), dst, len);
        ptr::write(dst.offset(len as isize + 1) as *mut u8, b'\0');
    }
}