1 // Take a look at the license at the top of the repository in the LICENSE file.
2 
3 use crate::translate::FromGlib;
4 use crate::translate::IntoGlib;
5 use libc::{c_char, c_uchar};
6 use std::convert::TryFrom;
7 use std::num::TryFromIntError;
8 
9 /// Wrapper for values where C functions expect a plain C `char`
10 ///
11 /// Consider the following C function prototype from glib:
12 ///
13 /// ```C
14 /// void g_key_file_set_list_separator (GKeyFile *key_file, gchar separator);
15 /// ```
16 ///
17 /// This function plainly expects a byte as the `separator` argument.  However,
18 /// having this function exposed to Rust as the following would be inconvenient:
19 ///
20 /// ```ignore
21 /// impl KeyFile {
22 ///     pub fn set_list_separator(&self, separator: libc:c_char) { }
23 /// }
24 /// ```
25 ///
26 /// This would be inconvenient because users would have to do the conversion from a Rust `char` to an `libc::c_char` by hand, which is just a type alias
27 /// for `i8` on most system.
28 ///
29 /// This `Char` type is a wrapper over an `libc::c_char`, so that we can pass it to Glib or C functions.
30 /// The check for whether a Rust `char` (a Unicode scalar value) actually fits in a `libc::c_char` is
31 /// done in the `new` function; see its documentation for details.
32 ///
33 /// The inner `libc::c_char` (which is equivalent to `i8`) can be extracted with `.0`, or
34 /// by calling `my_char.into_glib()`.
35 ///
36 /// # Examples
37 /// ```
38 /// use glib::Char;
39 /// use std::convert::TryFrom;
40 ///
41 /// Char::from(b'a');
42 /// Char::try_from('a').unwrap();
43 /// assert!(Char::try_from('☔').is_err());
44 /// ```
45 ///
46 /// ```ignore
47 /// extern "C" fn have_a_byte(b: libc::c_char);
48 ///
49 /// have_a_byte(Char::from(b'a').into_glib());
50 /// ```
51 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
52 pub struct Char(pub c_char);
53 
54 impl TryFrom<char> for Char {
55     type Error = TryFromIntError;
56 
57     #[allow(clippy::unnecessary_cast)]
try_from(c: char) -> Result<Char, Self::Error>58     fn try_from(c: char) -> Result<Char, Self::Error> {
59         Ok(Self(u8::try_from(u32::from(c))? as c_char))
60     }
61 }
62 
63 impl From<Char> for char {
from(c: Char) -> char64     fn from(c: Char) -> char {
65         c.0 as u8 as char
66     }
67 }
68 
69 impl From<u8> for Char {
70     #[allow(clippy::unnecessary_cast)]
from(c: u8) -> Char71     fn from(c: u8) -> Char {
72         Char(c as c_char)
73     }
74 }
75 
76 impl From<Char> for u8 {
77     #[allow(clippy::unnecessary_cast)]
from(c: Char) -> u878     fn from(c: Char) -> u8 {
79         c.0 as u8
80     }
81 }
82 
83 #[doc(hidden)]
84 impl FromGlib<c_char> for Char {
from_glib(value: c_char) -> Self85     unsafe fn from_glib(value: c_char) -> Self {
86         Self(value)
87     }
88 }
89 
90 #[doc(hidden)]
91 impl IntoGlib for Char {
92     type GlibType = c_char;
93 
into_glib(self) -> c_char94     fn into_glib(self) -> c_char {
95         self.0
96     }
97 }
98 
99 /// Wrapper for values where C functions expect a plain C `unsigned char`
100 ///
101 /// This `UChar` type is a wrapper over an `libc::c_uchar`, so that we can pass it to Glib or C functions.
102 /// The check for whether a Rust `char` (a Unicode scalar value) actually fits in a `libc::c_uchar` is
103 /// done in the `new` function; see its documentation for details.
104 ///
105 /// The inner `libc::c_uchar` (which is equivalent to `u8`) can be extracted with `.0`, or
106 /// by calling `my_char.into_glib()`.
107 ///
108 /// # Examples
109 /// ```
110 /// use glib::UChar;
111 /// use std::convert::TryFrom;
112 ///
113 /// UChar::from(b'a');
114 /// UChar::try_from('a').unwrap();
115 /// assert!(UChar::try_from('☔').is_err());
116 /// ```
117 ///
118 /// ```ignore
119 /// extern "C" fn have_a_byte(b: libc::c_uchar);
120 ///
121 /// have_a_byte(UChar::from(b'a').into_glib());
122 /// ```
123 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
124 pub struct UChar(pub c_uchar);
125 
126 impl TryFrom<char> for UChar {
127     type Error = TryFromIntError;
128 
129     #[allow(clippy::unnecessary_cast)]
try_from(c: char) -> Result<UChar, Self::Error>130     fn try_from(c: char) -> Result<UChar, Self::Error> {
131         Ok(Self(u8::try_from(u32::from(c))? as c_uchar))
132     }
133 }
134 
135 impl From<UChar> for char {
from(c: UChar) -> char136     fn from(c: UChar) -> char {
137         c.0 as char
138     }
139 }
140 
141 impl From<u8> for UChar {
142     #[allow(clippy::unnecessary_cast)]
from(c: u8) -> UChar143     fn from(c: u8) -> UChar {
144         UChar(c as c_uchar)
145     }
146 }
147 
148 impl From<UChar> for u8 {
from(c: UChar) -> u8149     fn from(c: UChar) -> u8 {
150         c.0 as u8
151     }
152 }
153 
154 #[doc(hidden)]
155 impl FromGlib<c_uchar> for UChar {
from_glib(value: c_uchar) -> Self156     unsafe fn from_glib(value: c_uchar) -> Self {
157         Self(value)
158     }
159 }
160 
161 #[doc(hidden)]
162 impl IntoGlib for UChar {
163     type GlibType = c_uchar;
164 
into_glib(self) -> c_uchar165     fn into_glib(self) -> c_uchar {
166         self.0
167     }
168 }
169 
170 #[cfg(test)]
171 mod tests {
172     use super::*;
173     use crate::translate::from_glib;
174 
175     #[test]
176     #[allow(clippy::unnecessary_cast)]
converts_single_byte_chars()177     fn converts_single_byte_chars() {
178         assert_eq!(Char::try_from(0 as char), Ok(Char(0 as c_char)));
179         assert_eq!(UChar::try_from(0 as char), Ok(UChar(0 as c_uchar)));
180         assert_eq!(UChar::try_from(255 as char), Ok(UChar(255 as c_uchar)));
181         assert_eq!(UChar::try_from('ñ'), Ok(UChar(241 as c_uchar)));
182     }
183 
184     #[test]
refuses_multibyte_chars()185     fn refuses_multibyte_chars() {
186         assert!(Char::try_from('☔').is_err()); // no umbrella for you
187         assert!(UChar::try_from('☔').is_err());
188     }
189 
190     #[test]
191     #[allow(clippy::unnecessary_cast)]
into_i8()192     fn into_i8() {
193         assert_eq!(Char::from(b'A').into_glib(), 65 as c_char);
194     }
195 
196     #[test]
197     #[allow(clippy::unnecessary_cast)]
into_u8()198     fn into_u8() {
199         assert_eq!(UChar::from(b'A').into_glib(), 65 as c_uchar);
200     }
201 
202     #[test]
203     #[allow(clippy::unnecessary_cast)]
into_char()204     fn into_char() {
205         assert_eq!(char::from(Char(65 as c_char)), 'A');
206         assert_eq!('ñ', UChar(241 as c_uchar).into());
207     }
208 
209     #[test]
210     #[allow(clippy::unnecessary_cast)]
convert_from_glib()211     fn convert_from_glib() {
212         assert_eq!(Char(65 as c_char), unsafe { from_glib(65 as c_char) });
213         assert_eq!(UChar(241 as c_uchar), unsafe { from_glib(241 as c_uchar) });
214     }
215 }
216