core_foundation/
string.rs

1// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
2// file at the top-level directory of this distribution.
3//
4// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. This file may not be copied, modified, or distributed
8// except according to those terms.
9
10//! Immutable strings.
11
12pub use core_foundation_sys::string::*;
13
14use crate::base::{CFIndexConvertible, TCFType};
15
16use core_foundation_sys::base::{kCFAllocatorDefault, kCFAllocatorNull};
17use core_foundation_sys::base::{Boolean, CFIndex, CFRange};
18use std::borrow::Cow;
19use std::ffi::CStr;
20use std::fmt;
21use std::ptr;
22use std::str::{self, FromStr};
23
24declare_TCFType! {
25    /// An immutable string in one of a variety of encodings.
26    CFString, CFStringRef
27}
28impl_TCFType!(CFString, CFStringRef, CFStringGetTypeID);
29
30impl FromStr for CFString {
31    type Err = ();
32
33    /// See also [`CFString::new()`] for a variant of this which does not return a `Result`.
34    #[inline]
35    fn from_str(string: &str) -> Result<CFString, ()> {
36        Ok(CFString::new(string))
37    }
38}
39
40impl<'a> From<&'a str> for CFString {
41    #[inline]
42    fn from(string: &'a str) -> CFString {
43        CFString::new(string)
44    }
45}
46
47impl<'a> From<&'a CFString> for Cow<'a, str> {
48    fn from(cf_str: &'a CFString) -> Cow<'a, str> {
49        unsafe {
50            // Do this without allocating if we can get away with it
51            let c_string = CFStringGetCStringPtr(cf_str.0, kCFStringEncodingUTF8);
52            if !c_string.is_null() {
53                let c_str = CStr::from_ptr(c_string);
54                Cow::Borrowed(str::from_utf8_unchecked(c_str.to_bytes()))
55            } else {
56                let char_len = cf_str.char_len();
57
58                // First, ask how big the buffer ought to be.
59                let mut bytes_required: CFIndex = 0;
60                CFStringGetBytes(
61                    cf_str.0,
62                    CFRange {
63                        location: 0,
64                        length: char_len,
65                    },
66                    kCFStringEncodingUTF8,
67                    0,
68                    false as Boolean,
69                    ptr::null_mut(),
70                    0,
71                    &mut bytes_required,
72                );
73
74                // Then, allocate the buffer and actually copy.
75                let mut buffer = vec![b'\x00'; bytes_required as usize];
76
77                let mut bytes_used: CFIndex = 0;
78                let chars_written = CFStringGetBytes(
79                    cf_str.0,
80                    CFRange {
81                        location: 0,
82                        length: char_len,
83                    },
84                    kCFStringEncodingUTF8,
85                    0,
86                    false as Boolean,
87                    buffer.as_mut_ptr(),
88                    buffer.len().to_CFIndex(),
89                    &mut bytes_used,
90                );
91                assert_eq!(chars_written, char_len);
92
93                // This is dangerous; we over-allocate and null-terminate the string (during
94                // initialization).
95                assert_eq!(bytes_used, buffer.len().to_CFIndex());
96                Cow::Owned(String::from_utf8_unchecked(buffer))
97            }
98        }
99    }
100}
101
102impl fmt::Display for CFString {
103    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
104        fmt.write_str(&Cow::from(self))
105    }
106}
107
108impl fmt::Debug for CFString {
109    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
110        write!(f, "\"{}\"", self)
111    }
112}
113
114impl CFString {
115    /// Creates a new `CFString` instance from a Rust string.
116    #[inline]
117    pub fn new(string: &str) -> CFString {
118        unsafe {
119            let string_ref = CFStringCreateWithBytes(
120                kCFAllocatorDefault,
121                string.as_ptr(),
122                string.len().to_CFIndex(),
123                kCFStringEncodingUTF8,
124                false as Boolean,
125            );
126            CFString::wrap_under_create_rule(string_ref)
127        }
128    }
129
130    /// Like `CFString::new`, but references a string that can be used as a backing store
131    /// by virtue of being statically allocated.
132    #[inline]
133    pub fn from_static_string(string: &'static str) -> CFString {
134        unsafe {
135            let string_ref = CFStringCreateWithBytesNoCopy(
136                kCFAllocatorDefault,
137                string.as_ptr(),
138                string.len().to_CFIndex(),
139                kCFStringEncodingUTF8,
140                false as Boolean,
141                kCFAllocatorNull,
142            );
143            TCFType::wrap_under_create_rule(string_ref)
144        }
145    }
146
147    /// Returns the number of characters in the string.
148    #[inline]
149    pub fn char_len(&self) -> CFIndex {
150        unsafe { CFStringGetLength(self.0) }
151    }
152}
153
154impl<'a> PartialEq<&'a str> for CFString {
155    fn eq(&self, other: &&str) -> bool {
156        unsafe {
157            let temp = CFStringCreateWithBytesNoCopy(
158                kCFAllocatorDefault,
159                other.as_ptr(),
160                other.len().to_CFIndex(),
161                kCFStringEncodingUTF8,
162                false as Boolean,
163                kCFAllocatorNull,
164            );
165            self.eq(&CFString::wrap_under_create_rule(temp))
166        }
167    }
168}
169
170impl<'a> PartialEq<CFString> for &'a str {
171    #[inline]
172    fn eq(&self, other: &CFString) -> bool {
173        other.eq(self)
174    }
175}
176
177impl PartialEq<CFString> for String {
178    #[inline]
179    fn eq(&self, other: &CFString) -> bool {
180        other.eq(&self.as_str())
181    }
182}
183
184impl PartialEq<String> for CFString {
185    #[inline]
186    fn eq(&self, other: &String) -> bool {
187        self.eq(&other.as_str())
188    }
189}
190
191#[test]
192fn str_cmp() {
193    let cfstr = CFString::new("hello");
194    assert_eq!("hello", cfstr);
195    assert_eq!(cfstr, "hello");
196    assert_ne!(cfstr, "wrong");
197    assert_ne!("wrong", cfstr);
198    let hello = String::from("hello");
199    assert_eq!(hello, cfstr);
200    assert_eq!(cfstr, hello);
201}
202
203#[test]
204fn string_and_back() {
205    let original = "The quick brown fox jumped over the slow lazy dog.";
206    let cfstr = CFString::from_static_string(original);
207    let converted = cfstr.to_string();
208    assert_eq!(converted, original);
209}