Skip to main content

unicode_names2/
lib.rs

1//! Convert between characters and their standard names.
2//!
3//! This crate provides two functions for mapping from a `char` to the
4//! name given by the Unicode standard (17.0). There are no runtime
5//! requirements so this is usable with only `core` (this requires
6//! specifying the `no_std` cargo feature). The tables are heavily
7//! compressed, but still large (500KB), and still offer efficient
8//! `O(1)` look-ups in both directions (more precisely, `O(length of
9//! name)`).
10//!
11//! ```rust
12//!     println!("☃ is called {:?}", unicode_names2::name('☃')); // SNOWMAN
13//!     println!("{:?} is happy", unicode_names2::character("white smiling face")); // ☺
14//!     // (NB. case insensitivity)
15//! ```
16//!
17//! [**Source**](https://github.com/ProgVal/unicode_names2).
18//!
19//! # Macros
20//!
21//! The associated `unicode_names2_macros` crate provides two macros
22//! for converting at compile-time, giving named literals similar to
23//! Python's `"\N{...}"`.
24//!
25//! - `named_char!(name)` takes a single string `name` and creates a
26//!   `char` literal.
27//! - `named!(string)` takes a string and replaces any `\\N{name}`
28//!   sequences with the character with that name. NB. String escape
29//!   sequences cannot be customised, so the extra backslash (or a raw
30//!   string) is required, unless you use a raw string.
31//!
32//! ```rust,ignore
33//! #![feature(proc_macro_hygiene)]
34//!
35//! #[macro_use]
36//! extern crate unicode_names2_macros;
37//!
38//! fn main() {
39//!     let x: char = named_char!("snowman");
40//!     assert_eq!(x, '☃');
41//!
42//!     let y: &str = named!("foo bar \\N{BLACK STAR} baz qux");
43//!     assert_eq!(y, "foo bar ★ baz qux");
44//!
45//!     let y: &str = named!(r"foo bar \N{BLACK STAR} baz qux");
46//!     assert_eq!(y, "foo bar ★ baz qux");
47//! }
48//! ```
49
50#![cfg_attr(feature = "no_std", no_std)]
51#![cfg_attr(test, feature(test))]
52#![deny(missing_docs, unsafe_code)]
53
54#[cfg(all(test, feature = "no_std"))]
55#[macro_use]
56extern crate std;
57
58use core::{char, fmt};
59use generated::{
60    MAX_NAME_LENGTH, PHRASEBOOK_OFFSETS1, PHRASEBOOK_OFFSETS2, PHRASEBOOK_OFFSET_SHIFT,
61};
62
63#[allow(dead_code)]
64#[rustfmt::skip]
65#[allow(clippy::all)]
66mod generated {
67    include!(concat!(env!("OUT_DIR"), "/generated.rs"));
68}
69#[allow(dead_code)]
70#[rustfmt::skip]
71#[allow(clippy::all)]
72mod generated_phf {
73    include!(concat!(env!("OUT_DIR"), "/generated_phf.rs"));
74}
75#[allow(dead_code)]
76mod jamo;
77
78/// A map of unicode aliases to their corresponding values.
79/// Generated in generator
80#[allow(dead_code)]
81static ALIASES: phf::Map<&'static [u8], char> =
82    include!(concat!(env!("OUT_DIR"), "/generated_alias.rs"));
83
84mod iter_str;
85
86static HANGUL_SYLLABLE_PREFIX: &str = "HANGUL SYLLABLE ";
87static CJK_UNIFIED_IDEOGRAPH_PREFIX: &str = "CJK UNIFIED IDEOGRAPH-";
88
89fn is_cjk_unified_ideograph(ch: char) -> bool {
90    generated::CJK_IDEOGRAPH_RANGES
91        .iter()
92        .any(|&(lo, hi)| lo <= ch && ch <= hi)
93}
94
95/// An iterator over the components of a code point's name, it also
96/// implements `Show`.
97///
98/// The size hint is exact for the number of pieces, but iterates
99/// (although iteration is cheap and all names are short).
100#[derive(Clone)]
101pub struct Name {
102    data: Name_,
103}
104#[allow(clippy::upper_case_acronyms)]
105#[derive(Clone)]
106enum Name_ {
107    Plain(iter_str::IterStr),
108    CJK(CJK),
109    Hangul(Hangul),
110}
111
112#[allow(clippy::upper_case_acronyms)]
113#[derive(Copy)]
114struct CJK {
115    emit_prefix: bool,
116    idx: u8,
117    // the longest character is 0x10FFFF
118    data: [u8; 6],
119}
120#[derive(Copy)]
121struct Hangul {
122    emit_prefix: bool,
123    idx: u8,
124    // stores the choseong, jungseong, jongseong syllable numbers (in
125    // that order)
126    data: [u8; 3],
127}
128impl Clone for CJK {
129    fn clone(&self) -> CJK {
130        *self
131    }
132}
133impl Clone for Hangul {
134    fn clone(&self) -> Hangul {
135        *self
136    }
137}
138
139#[allow(clippy::len_without_is_empty)]
140impl Name {
141    /// The number of bytes in the name.
142    ///
143    /// All names are plain ASCII, so this is also the number of
144    /// Unicode codepoints and the number of graphemes.
145    pub fn len(&self) -> usize {
146        let counted = self.clone();
147        counted.fold(0, |a, s| a + s.len())
148    }
149}
150
151impl Iterator for Name {
152    type Item = &'static str;
153    fn next(&mut self) -> Option<&'static str> {
154        match self.data {
155            Name_::Plain(ref mut s) => s.next(),
156            Name_::CJK(ref mut state) => {
157                // we're a CJK unified ideograph
158                if state.emit_prefix {
159                    state.emit_prefix = false;
160                    return Some(CJK_UNIFIED_IDEOGRAPH_PREFIX);
161                }
162                // run until we've run out of array: the construction
163                // of the data means this is exactly when we have
164                // finished emitting the number.
165                state
166                    .data
167                    .get(state.idx as usize)
168                    // (avoid conflicting mutable borrow problems)
169                    .map(|digit| *digit as usize)
170                    .map(|d| {
171                        state.idx += 1;
172                        static DIGITS: &str = "0123456789ABCDEF";
173                        &DIGITS[d..d + 1]
174                    })
175            }
176            Name_::Hangul(ref mut state) => {
177                if state.emit_prefix {
178                    state.emit_prefix = false;
179                    return Some(HANGUL_SYLLABLE_PREFIX);
180                }
181
182                let idx = state.idx as usize;
183                state.data.get(idx).map(|x| *x as usize).map(|x| {
184                    // progressively walk through the syllables
185                    state.idx += 1;
186                    [jamo::CHOSEONG, jamo::JUNGSEONG, jamo::JONGSEONG][idx][x]
187                })
188            }
189        }
190    }
191
192    fn size_hint(&self) -> (usize, Option<usize>) {
193        // we can estimate exactly by just iterating and summing up.
194        let counted = self.clone();
195        let n = counted.count();
196        (n, Some(n))
197    }
198}
199
200impl fmt::Debug for Name {
201    fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result {
202        fmt::Display::fmt(self, fmtr)
203    }
204}
205impl fmt::Display for Name {
206    fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result {
207        self.clone().try_for_each(|s| fmtr.write_str(s))
208    }
209}
210
211/// Find the name of `c`, or `None` if `c` has no name.
212///
213/// The return value is an iterator that yields `&str` components of
214/// the name successively (including spaces and hyphens). It
215/// implements `Show`, and thus can be used naturally to build
216/// `String`s, or be printed, etc.
217///
218/// # Example
219///
220/// ```rust
221/// assert_eq!(unicode_names2::name('a').map(|n| n.to_string()),
222///            Some("LATIN SMALL LETTER A".to_string()));
223/// assert_eq!(unicode_names2::name('\u{2605}').map(|n| n.to_string()),
224///            Some("BLACK STAR".to_string()));
225/// assert_eq!(unicode_names2::name('☃').map(|n| n.to_string()),
226///            Some("SNOWMAN".to_string()));
227///
228/// // control code
229/// assert!(unicode_names2::name('\x00').is_none());
230/// // unassigned
231/// assert!(unicode_names2::name('\u{10FFFF}').is_none());
232/// ```
233pub fn name(c: char) -> Option<Name> {
234    let cc = c as usize;
235    let offset =
236        (PHRASEBOOK_OFFSETS1[cc >> PHRASEBOOK_OFFSET_SHIFT] as usize) << PHRASEBOOK_OFFSET_SHIFT;
237
238    let mask = (1 << PHRASEBOOK_OFFSET_SHIFT) - 1;
239    let offset = PHRASEBOOK_OFFSETS2[offset + (cc & mask)];
240    if offset == 0 {
241        if is_cjk_unified_ideograph(c) {
242            // write the hex number out right aligned in this array.
243            let mut data = [b'0'; 6];
244            let mut number = c as u32;
245            let mut data_start = 6;
246            for place in data.iter_mut().rev() {
247                // this would be incorrect if U+0000 was CJK unified
248                // ideograph, but it's not, so it's fine.
249                if number == 0 {
250                    break;
251                }
252                *place = (number % 16) as u8;
253                number /= 16;
254                data_start -= 1;
255            }
256            Some(Name {
257                data: Name_::CJK(CJK {
258                    emit_prefix: true,
259                    idx: data_start,
260                    data,
261                }),
262            })
263        } else {
264            // maybe it is a hangul syllable?
265            jamo::syllable_decomposition(c).map(|(ch, ju, jo)| Name {
266                data: Name_::Hangul(Hangul {
267                    emit_prefix: true,
268                    idx: 0,
269                    data: [ch, ju, jo],
270                }),
271            })
272        }
273    } else {
274        Some(Name {
275            data: Name_::Plain(iter_str::IterStr::new(offset)),
276        })
277    }
278}
279
280fn fnv_hash<I: Iterator<Item = u8>>(x: I) -> u64 {
281    let mut g = 0xcbf29ce484222325 ^ generated_phf::NAME2CODE_N;
282    for b in x {
283        g ^= b as u64;
284        g = g.wrapping_mul(0x100000001b3);
285    }
286    g
287}
288fn displace(f1: u32, f2: u32, d1: u32, d2: u32) -> u32 {
289    d2.wrapping_add(f1.wrapping_mul(d1)).wrapping_add(f2)
290}
291fn split(hash: u64) -> (u32, u32, u32) {
292    let bits = 21;
293    let mask = (1 << bits) - 1;
294    (
295        (hash & mask) as u32,
296        ((hash >> bits) & mask) as u32,
297        ((hash >> (2 * bits)) & mask) as u32,
298    )
299}
300
301/// Get alias value from alias name, returns `None` if the alias is not found.
302fn character_by_alias(name: &[u8]) -> Option<char> {
303    ALIASES.get(name).copied()
304}
305
306/// Find the character called `name`, or `None` if no such character
307/// exists.
308///
309/// This searches case-insensitively.
310///
311/// # Example
312///
313/// ```rust
314/// assert_eq!(unicode_names2::character("LATIN SMALL LETTER A"), Some('a'));
315/// assert_eq!(unicode_names2::character("latin SMALL letter A"), Some('a'));
316/// assert_eq!(unicode_names2::character("latin small letter a"), Some('a'));
317/// assert_eq!(unicode_names2::character("BLACK STAR"), Some('★'));
318/// assert_eq!(unicode_names2::character("SNOWMAN"), Some('☃'));
319/// assert_eq!(unicode_names2::character("BACKSPACE"), Some('\x08'));
320///
321/// assert_eq!(unicode_names2::character("nonsense"), None);
322/// ```
323pub fn character(search_name: &str) -> Option<char> {
324    // + 1 so that we properly handle the case when `name` has a
325    // prefix of the longest name, but isn't exactly equal.
326    let mut buf = [0; MAX_NAME_LENGTH + 1];
327    for (place, byte) in buf.iter_mut().zip(search_name.bytes()) {
328        *place = byte.to_ascii_uppercase();
329    }
330    let search_name = buf.get(..search_name.len())?;
331
332    // try `HANGUL SYLLABLE <choseong><jungseong><jongseong>`
333    if search_name.starts_with(HANGUL_SYLLABLE_PREFIX.as_bytes()) {
334        let remaining = &search_name[HANGUL_SYLLABLE_PREFIX.len()..];
335        let (choseong, remaining) = jamo::slice_shift_choseong(remaining);
336        let (jungseong, remaining) = jamo::slice_shift_jungseong(remaining);
337        let (jongseong, remaining) = jamo::slice_shift_jongseong(remaining);
338        match (choseong, jungseong, jongseong, remaining) {
339            (Some(choseong), Some(jungseong), Some(jongseong), b"") => {
340                let c = 0xac00 + (choseong * 21 + jungseong) * 28 + jongseong;
341                return char::from_u32(c);
342            }
343            (_, _, _, _) => {
344                // there are no other names starting with `HANGUL SYLLABLE `
345                // (verified by `generator/...`).
346                return None;
347            }
348        }
349    }
350
351    // try `CJK UNIFIED IDEOGRAPH-<digits>`
352    if search_name.starts_with(CJK_UNIFIED_IDEOGRAPH_PREFIX.as_bytes()) {
353        let remaining = &search_name[CJK_UNIFIED_IDEOGRAPH_PREFIX.len()..];
354        if remaining.len() > 5 {
355            return None;
356        } // avoid overflow
357
358        let mut v = 0u32;
359        for &c in remaining {
360            v = match c {
361                b'0'..=b'9' => (v << 4) | (c - b'0') as u32,
362                b'A'..=b'F' => (v << 4) | (c - b'A' + 10) as u32,
363                _ => return None,
364            }
365        }
366        let ch = match char::from_u32(v) {
367            Some(ch) => ch,
368            None => return None,
369        };
370
371        // check if the resulting code is indeed in the known ranges
372        if is_cjk_unified_ideograph(ch) {
373            return Some(ch);
374        } else {
375            // there are no other names starting with `CJK UNIFIED IDEOGRAPH-`
376            // (verified by `src/generate.py`).
377            return None;
378        }
379    }
380
381    // get the parts of the hash...
382    let (g, f1, f2) = split(fnv_hash(search_name.iter().copied()));
383    // ...and the appropriate displacements...
384    let (d1, d2) = generated_phf::NAME2CODE_DISP[g as usize % generated_phf::NAME2CODE_DISP.len()];
385
386    // ...to find the right index...
387    let idx = displace(f1, f2, d1 as u32, d2 as u32) as usize;
388    // ...for looking up the codepoint.
389    let codepoint = generated_phf::NAME2CODE_CODE[idx % generated_phf::NAME2CODE_CODE.len()];
390
391    // Now check that this is actually correct. Since this is a
392    // perfect hash table, valid names map precisely to their code
393    // point (and invalid names map to anything), so we only need to
394    // check the name for this codepoint matches the input and we know
395    // everything. (i.e. no need for probing)
396    let maybe_name = match name(codepoint) {
397        None => {
398            if true {
399                debug_assert!(false)
400            }
401            return character_by_alias(search_name);
402        }
403        Some(name) => name,
404    };
405
406    // run through the parts of the name, matching them against the
407    // parts of the input.
408    let mut passed_name = search_name;
409    for part in maybe_name {
410        let part = part.as_bytes();
411        let part_l = part.len();
412        if passed_name.len() < part_l || &passed_name[..part_l] != part {
413            return character_by_alias(search_name);
414        }
415        passed_name = &passed_name[part_l..]
416    }
417
418    Some(codepoint)
419}
420
421#[cfg(test)]
422mod tests {
423    use super::*;
424    use rand::{
425        distributions::{Distribution, Standard},
426        prelude::{SeedableRng, StdRng},
427    };
428    use std::char;
429    use std::prelude::v1::*;
430
431    extern crate test;
432
433    use test::bench::Bencher;
434
435    static DATA: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/data/UnicodeData.txt"));
436
437    #[test]
438    fn exhaustive() {
439        // check that gaps have no names (these are unassigned/control
440        // codes).
441        fn negative_range(from: u32, to: u32) {
442            for c in (from..to).filter_map(char::from_u32) {
443                if !is_cjk_unified_ideograph(c) && !jamo::is_hangul_syllable(c) {
444                    let n = name(c);
445                    assert!(
446                        n.is_none(),
447                        "{} ({}) shouldn't have a name but is called {}",
448                        c,
449                        c as u32,
450                        n.unwrap()
451                    );
452                }
453            }
454        }
455
456        let mut last = 0;
457        for line in DATA.lines() {
458            let mut it = line.split(';');
459
460            let raw_c = it.next();
461            let c = match char::from_u32(
462                raw_c.and_then(|s| u32::from_str_radix(s, 16).ok()).unwrap(),
463            ) {
464                Some(c) => c,
465                None => continue,
466            };
467
468            let n = it.next().unwrap();
469            if n.starts_with("<") {
470                continue;
471            }
472
473            let computed_n = name(c).unwrap();
474            let n_str = computed_n.to_string();
475            assert_eq!(n_str, n.to_string());
476            assert_eq!(computed_n.len(), n_str.len());
477
478            let (hint_low, hint_high) = computed_n.size_hint();
479            let number_of_parts = computed_n.count();
480            assert_eq!(hint_low, number_of_parts);
481            assert_eq!(hint_high, Some(number_of_parts));
482
483            assert_eq!(character(n), Some(c));
484            assert_eq!(character(&n.to_ascii_lowercase()), Some(c));
485
486            negative_range(last, c as u32);
487            last = c as u32 + 1;
488        }
489        negative_range(last, 0x10FFFF + 1)
490    }
491
492    #[test]
493    fn name_to_string() {
494        let n = name('a').unwrap();
495        assert_eq!(n.to_string(), "LATIN SMALL LETTER A".to_string());
496        let n = name('🁣').unwrap();
497        assert_eq!(n.to_string(), "DOMINO TILE VERTICAL-00-00".to_string());
498    }
499
500    #[test]
501    fn character_negative() {
502        let long_name = "x".repeat(100);
503        assert!(long_name.len() > MAX_NAME_LENGTH); // Otherwise this test is pointless
504        let names = ["", "x", "öäå", "SPAACE", &long_name];
505        for &n in names.iter() {
506            assert_eq!(character(n), None);
507        }
508    }
509
510    #[test]
511    fn name_hangul_syllable() {
512        assert_eq!(
513            name('\u{ac00}').map(|s| s.to_string()),
514            Some("HANGUL SYLLABLE GA".to_string())
515        ); // first
516        assert_eq!(
517            name('\u{bdc1}').map(|s| s.to_string()),
518            Some("HANGUL SYLLABLE BWELG".to_string())
519        );
520        assert_eq!(
521            name('\u{d7a3}').map(|s| s.to_string()),
522            Some("HANGUL SYLLABLE HIH".to_string())
523        ); // last
524    }
525
526    #[test]
527    fn character_hangul_syllable() {
528        assert_eq!(character("HANGUL SYLLABLE GA"), Some('\u{ac00}'));
529        assert_eq!(character("HANGUL SYLLABLE BWELG"), Some('\u{bdc1}'));
530        assert_eq!(character("HANGUL SYLLABLE HIH"), Some('\u{d7a3}'));
531        assert_eq!(character("HANGUL SYLLABLE BLAH"), None);
532    }
533
534    #[test]
535    fn cjk_unified_ideograph_exhaustive() {
536        for &(lo, hi) in generated::CJK_IDEOGRAPH_RANGES.iter() {
537            for x in lo as u32..=hi as u32 {
538                let c = char::from_u32(x).unwrap();
539
540                let real_name = format!("CJK UNIFIED IDEOGRAPH-{:X}", x);
541                let lower_real_name = format!("CJK UNIFIED IDEOGRAPH-{:x}", x);
542                assert_eq!(character(&real_name), Some(c));
543                assert_eq!(character(&lower_real_name), Some(c));
544
545                assert_eq!(name(c).map(|s| s.to_string()), Some(real_name));
546            }
547        }
548    }
549    #[test]
550    fn name_cjk_unified_ideograph() {
551        assert_eq!(
552            name('\u{4e00}').map(|s| s.to_string()),
553            Some("CJK UNIFIED IDEOGRAPH-4E00".to_string())
554        ); // first in BMP
555        assert_eq!(
556            name('\u{9fcc}').map(|s| s.to_string()),
557            Some("CJK UNIFIED IDEOGRAPH-9FCC".to_string())
558        ); // last in BMP (as of 6.1)
559        assert_eq!(
560            name('\u{20000}').map(|s| s.to_string()),
561            Some("CJK UNIFIED IDEOGRAPH-20000".to_string())
562        ); // first in SIP
563        assert_eq!(
564            name('\u{2a6d6}').map(|s| s.to_string()),
565            Some("CJK UNIFIED IDEOGRAPH-2A6D6".to_string())
566        );
567        assert_eq!(
568            name('\u{2a700}').map(|s| s.to_string()),
569            Some("CJK UNIFIED IDEOGRAPH-2A700".to_string())
570        );
571        assert_eq!(
572            name('\u{2b81d}').map(|s| s.to_string()),
573            Some("CJK UNIFIED IDEOGRAPH-2B81D".to_string())
574        ); // last in SIP (as of 6.0)
575    }
576
577    #[test]
578    fn character_cjk_unified_ideograph() {
579        assert_eq!(character("CJK UNIFIED IDEOGRAPH-4E00"), Some('\u{4e00}'));
580        assert_eq!(character("CJK UNIFIED IDEOGRAPH-9FCC"), Some('\u{9fcc}'));
581        assert_eq!(character("CJK UNIFIED IDEOGRAPH-20000"), Some('\u{20000}'));
582        assert_eq!(character("CJK UNIFIED IDEOGRAPH-2A6D6"), Some('\u{2a6d6}'));
583        assert_eq!(character("CJK UNIFIED IDEOGRAPH-2A700"), Some('\u{2a700}'));
584        assert_eq!(character("CJK UNIFIED IDEOGRAPH-2B81D"), Some('\u{2b81d}'));
585        assert_eq!(character("CJK UNIFIED IDEOGRAPH-"), None);
586        assert_eq!(character("CJK UNIFIED IDEOGRAPH-!@#$"), None);
587        assert_eq!(character("CJK UNIFIED IDEOGRAPH-1234"), None);
588        assert_eq!(character("CJK UNIFIED IDEOGRAPH-EFGH"), None);
589        assert_eq!(character("CJK UNIFIED IDEOGRAPH-12345"), None);
590        assert_eq!(character("CJK UNIFIED IDEOGRAPH-2A6FF"), None); // between Ext B and Ext C
591        assert_eq!(character("CJK UNIFIED IDEOGRAPH-2A6FF"), None);
592    }
593
594    #[test]
595    fn character_by_alias() {
596        assert_eq!(super::character_by_alias(b"NEW LINE"), Some('\n'));
597        assert_eq!(super::character_by_alias(b"BACKSPACE"), Some('\u{8}'));
598        assert_eq!(super::character_by_alias(b"NOT AN ALIAS"), None);
599    }
600
601    #[bench]
602    fn name_basic(b: &mut Bencher) {
603        b.iter(|| {
604            for s in name('ö').unwrap() {
605                test::black_box(s);
606            }
607        })
608    }
609
610    #[bench]
611    fn character_basic(b: &mut Bencher) {
612        b.iter(|| character("LATIN SMALL LETTER O WITH DIAERESIS"));
613    }
614
615    #[bench]
616    fn name_10000_invalid(b: &mut Bencher) {
617        // be consistent across runs, but avoid sequential/caching.
618        let mut rng = StdRng::seed_from_u64(0x12345678);
619        let chars: Vec<char> = Standard
620            .sample_iter(&mut rng)
621            .take(10000)
622            .filter_map(|c| match c {
623                c if name(c).is_none() => Some(c),
624                _ => None,
625            })
626            .collect();
627
628        b.iter(|| {
629            for &c in chars.iter() {
630                assert!(name(c).is_none());
631            }
632        })
633    }
634
635    #[bench]
636    fn name_all_valid(b: &mut Bencher) {
637        let chars = (0u32..0x10FFFF)
638            .filter_map(|x| match char::from_u32(x) {
639                Some(c) if name(c).is_some() => Some(c),
640                _ => None,
641            })
642            .collect::<Vec<char>>();
643
644        b.iter(|| {
645            for c in chars.iter() {
646                for s in name(*c).unwrap() {
647                    test::black_box(s);
648                }
649            }
650        });
651    }
652
653    #[bench]
654    fn character_10000(b: &mut Bencher) {
655        // be consistent across runs, but avoid sequential/caching.
656        let mut rng = StdRng::seed_from_u64(0x12345678);
657
658        let names: Vec<_> = Standard
659            .sample_iter(&mut rng)
660            .take(10000)
661            .filter_map(name)
662            .map(|name| name.to_string())
663            .collect();
664
665        b.iter(|| {
666            for n in names.iter() {
667                test::black_box(character(n));
668            }
669        })
670    }
671}
672
673#[cfg(all(feature = "no_std", not(test)))]
674mod std {
675    pub use core::{clone, fmt, marker};
676}