Thanks to visit codestin.com
Credit goes to docs.rs

magnus/
encoding.rs

1//! Types and functions for working with encodings.
2//!
3//! This module defines 3 types for working with encodings, these types can
4//! be converted back and forth with [`From`]/[`Into`] like so:
5//! ``` text
6//! Encoding <-> RbEncoding <-> Index
7//!       |______________________^
8//! ```
9//! Many functions that require an encoding take their arguments as
10//! `Into<RbEncoding>` or `Into<Index>` to ease working with the different
11//! types. The type specified for the `Into` conversion hints at the type the
12//! function natively works with, and thus will avoid any conversion cost.
13//!
14//! [`Encoding`] and [`RbEncoding`] both implement [`TryConvert`] and
15//! [`IntoValue`] so can be used as parameters and return values in functions
16//! bound to Ruby. Both convert from either an instance of `Encoding` or a
17//! string of an encoding name, and convert to an instance of `Encoding`.
18
19use std::{
20    ffi::{CStr, CString},
21    fmt,
22    ops::Range,
23    os::raw::{c_char, c_int},
24    ptr::{self, NonNull},
25};
26
27use rb_sys::{
28    rb_ascii8bit_encindex, rb_ascii8bit_encoding, rb_default_external_encoding,
29    rb_default_internal_encoding, rb_enc_ascget, rb_enc_associate_index, rb_enc_check,
30    rb_enc_codelen, rb_enc_codepoint_len, rb_enc_compatible, rb_enc_copy, rb_enc_default_external,
31    rb_enc_default_internal, rb_enc_fast_mbclen, rb_enc_find, rb_enc_find_index,
32    rb_enc_from_encoding, rb_enc_from_index, rb_enc_get_index, rb_enc_mbclen,
33    rb_enc_precise_mbclen, rb_enc_set_index, rb_enc_to_index, rb_enc_uint_chr, rb_encoding,
34    rb_filesystem_encindex, rb_filesystem_encoding, rb_find_encoding, rb_locale_encindex,
35    rb_locale_encoding, rb_to_encoding, rb_to_encoding_index, rb_usascii_encindex,
36    rb_usascii_encoding, rb_utf8_encindex, rb_utf8_encoding,
37};
38
39use crate::{
40    error::{protect, Error},
41    into_value::IntoValue,
42    object::Object,
43    r_string::RString,
44    try_convert::TryConvert,
45    value::{
46        private::{self, ReprValue as _},
47        NonZeroValue, ReprValue, Value,
48    },
49    Ruby,
50};
51
52/// # `Encoding`
53///
54/// Functions to access pre-defined Encodings.
55///
56/// See also the [`Encoding`] type.
57impl Ruby {
58    /// Returns the default internal encoding as a Ruby object.
59    ///
60    /// This is the encoding used for anything out-of-process, such as reading
61    /// from files or sockets.
62    pub fn enc_default_external(&self) -> Encoding {
63        Encoding::from_value(Value::new(unsafe { rb_enc_default_external() })).unwrap()
64    }
65
66    /// Returns the default external encoding as a Ruby object.
67    ///
68    /// If set, any out-of-process data is transcoded from the default external
69    /// encoding to the default internal encoding.
70    pub fn enc_default_internal(&self) -> Option<Encoding> {
71        Encoding::from_value(Value::new(unsafe { rb_enc_default_internal() }))
72    }
73}
74
75/// Wrapper type for a Value known to be an instance of Ruby's Encoding class.
76///
77/// This is the representation of an encoding exposed to Ruby code.
78///
79/// See the [`ReprValue`] and [`Object`] traits for additional methods
80/// available on this type. See [`Ruby`](Ruby#encoding) for methods to get an
81/// `Encoding`.
82#[derive(Clone, Copy)]
83#[repr(transparent)]
84pub struct Encoding(NonZeroValue);
85
86impl Encoding {
87    /// Return `Some(Encoding)` if `val` is an `Encoding`, `None` otherwise.
88    ///
89    /// # Examples
90    ///
91    /// ```
92    /// use magnus::{encoding::Encoding, eval};
93    /// # let _cleanup = unsafe { magnus::embed::init() };
94    ///
95    /// assert!(Encoding::from_value(eval("Encoding::US_ASCII").unwrap()).is_some());
96    /// assert!(Encoding::from_value(eval("nil").unwrap()).is_none());
97    /// ```
98    #[inline]
99    pub fn from_value(val: Value) -> Option<Self> {
100        unsafe {
101            val.is_kind_of(Ruby::get_with(val).class_encoding())
102                .then(|| Self(NonZeroValue::new_unchecked(val)))
103        }
104    }
105
106    /// Returns the default internal encoding as a Ruby object.
107    ///
108    /// This is the encoding used for anything out-of-process, such as reading
109    /// from files or sockets.
110    ///
111    /// # Panics
112    ///
113    /// Panics if called from a non-Ruby thread. See
114    /// [`Ruby::enc_default_external`] for the non-panicking version.
115    #[cfg_attr(
116        not(feature = "old-api"),
117        deprecated(note = "please use `Ruby::enc_default_external` instead")
118    )]
119    #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
120    #[inline]
121    pub fn default_external() -> Self {
122        get_ruby!().enc_default_external()
123    }
124
125    /// Returns the default external encoding as a Ruby object.
126    ///
127    /// If set, any out-of-process data is transcoded from the default external
128    /// encoding to the default internal encoding.
129    ///
130    /// # Panics
131    ///
132    /// Panics if called from a non-Ruby thread. See
133    /// [`Ruby::enc_default_internal`] for the non-panicking version.
134    #[cfg_attr(
135        not(feature = "old-api"),
136        deprecated(note = "please use `Ruby::enc_default_internal` instead")
137    )]
138    #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
139    #[inline]
140    pub fn default_internal() -> Option<Self> {
141        get_ruby!().enc_default_internal()
142    }
143}
144
145impl fmt::Display for Encoding {
146    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147        write!(f, "{}", unsafe { self.to_s_infallible() })
148    }
149}
150
151impl fmt::Debug for Encoding {
152    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153        write!(f, "{}", self.inspect())
154    }
155}
156
157impl From<Encoding> for Index {
158    fn from(val: Encoding) -> Self {
159        let i = unsafe { rb_to_encoding_index(val.as_rb_value()) };
160        if i == -1 {
161            panic!("got encoding index -1");
162        }
163        Index(i)
164    }
165}
166
167impl From<Encoding> for RbEncoding {
168    fn from(val: Encoding) -> Self {
169        let ptr = unsafe { rb_find_encoding(val.as_rb_value()) };
170        RbEncoding::new(ptr).expect("got NULL rb_encoding")
171    }
172}
173
174impl IntoValue for Encoding {
175    #[inline]
176    fn into_value_with(self, _: &Ruby) -> Value {
177        self.0.get()
178    }
179}
180
181impl Object for Encoding {}
182
183unsafe impl private::ReprValue for Encoding {}
184
185impl ReprValue for Encoding {}
186
187impl TryConvert for Encoding {
188    fn try_convert(val: Value) -> Result<Self, Error> {
189        if let Some(enc) = Self::from_value(val) {
190            return Ok(enc);
191        }
192        RbEncoding::try_convert(val).map(Into::into)
193    }
194}
195
196/// # `RbEncoding`
197///
198/// Functions to access pre-defined encodings.
199///
200/// See also the [`RbEncoding`] type.
201impl Ruby {
202    /// Returns the encoding that represents ASCII-8BIT a.k.a. binary.
203    ///
204    /// # Examples
205    ///
206    /// ```
207    /// use magnus::{Error, Ruby};
208    ///
209    /// fn example(ruby: &Ruby) -> Result<(), Error> {
210    ///     assert_eq!(ruby.ascii8bit_encoding().name(), "ASCII-8BIT");
211    ///     Ok(())
212    /// }
213    /// # Ruby::init(example).unwrap()
214    /// ```
215    pub fn ascii8bit_encoding(&self) -> RbEncoding {
216        RbEncoding::new(unsafe { rb_ascii8bit_encoding() }).unwrap()
217    }
218
219    /// Returns the encoding that represents UTF-8.
220    ///
221    /// # Examples
222    ///
223    /// ```
224    /// use magnus::{Error, Ruby};
225    ///
226    /// fn example(ruby: &Ruby) -> Result<(), Error> {
227    ///     assert_eq!(ruby.utf8_encoding().name(), "UTF-8");
228    ///     Ok(())
229    /// }
230    /// # Ruby::init(example).unwrap()
231    /// ```
232    pub fn utf8_encoding(&self) -> RbEncoding {
233        RbEncoding::new(unsafe { rb_utf8_encoding() }).unwrap()
234    }
235
236    /// Returns the encoding that represents US-ASCII.
237    ///
238    /// # Examples
239    ///
240    /// ```
241    /// use magnus::{Error, Ruby};
242    ///
243    /// fn example(ruby: &Ruby) -> Result<(), Error> {
244    ///     assert_eq!(ruby.usascii_encoding().name(), "US-ASCII");
245    ///     Ok(())
246    /// }
247    /// # Ruby::init(example).unwrap()
248    /// ```
249    pub fn usascii_encoding(&self) -> RbEncoding {
250        RbEncoding::new(unsafe { rb_usascii_encoding() }).unwrap()
251    }
252
253    /// Returns the encoding that represents the process' current locale.
254    ///
255    /// This is dynamic. If you change the process' locale that should also
256    /// change the return value of this function.
257    pub fn locale_encoding(&self) -> RbEncoding {
258        RbEncoding::new(unsafe { rb_locale_encoding() }).unwrap()
259    }
260
261    /// Returns the filesystem encoding.
262    ///
263    /// This is the encoding that Ruby expects data from the OS' file system
264    /// to be encoded as, such as directory names.
265    pub fn filesystem_encoding(&self) -> RbEncoding {
266        RbEncoding::new(unsafe { rb_filesystem_encoding() }).unwrap()
267    }
268
269    /// Returns the default external encoding.
270    ///
271    /// This is the encoding used for anything out-of-process, such as reading
272    /// from files or sockets.
273    pub fn default_external_encoding(&self) -> RbEncoding {
274        RbEncoding::new(unsafe { rb_default_external_encoding() }).unwrap()
275    }
276
277    /// Returns the default internal encoding.
278    ///
279    /// If set, any out-of-process data is transcoded from the default external
280    /// encoding to the default internal encoding.
281    pub fn default_internal_encoding(&self) -> Option<RbEncoding> {
282        RbEncoding::new(unsafe { rb_default_internal_encoding() })
283    }
284
285    /// Returns the encoding with the name or alias `name`.
286    ///
287    /// # Examples
288    ///
289    /// ```
290    /// use magnus::{Error, Ruby};
291    ///
292    /// fn example(ruby: &Ruby) -> Result<(), Error> {
293    ///     assert_eq!(ruby.find_encoding("BINARY").unwrap().name(), "ASCII-8BIT");
294    ///     assert_eq!(ruby.find_encoding("UTF-8").unwrap().name(), "UTF-8");
295    ///     Ok(())
296    /// }
297    /// # Ruby::init(example).unwrap()
298    /// ```
299    pub fn find_encoding(&self, name: &str) -> Option<RbEncoding> {
300        let name = CString::new(name).unwrap();
301        let ptr = unsafe { rb_enc_find(name.as_ptr()) };
302        RbEncoding::new(ptr)
303    }
304}
305
306/// Ruby's internal encoding type.
307///
308/// This type contains the data for an encoding, and is used with operations
309/// such as converting a string from one encoding to another, or reading a
310/// string character by character.
311///
312/// See [`Ruby`](Ruby#rbencoding) for methods to get an `RbEncoding`.
313#[repr(transparent)]
314pub struct RbEncoding(NonNull<rb_encoding>);
315
316impl RbEncoding {
317    pub(crate) fn new(inner: *mut rb_encoding) -> Option<Self> {
318        NonNull::new(inner).map(Self)
319    }
320
321    /// Returns the encoding that represents ASCII-8BIT a.k.a. binary.
322    ///
323    /// # Panics
324    ///
325    /// Panics if called from a non-Ruby thread. See
326    /// [`Ruby::ascii8bit_encoding`] for the non-panicking version.
327    #[cfg_attr(
328        not(feature = "old-api"),
329        deprecated(note = "please use `Ruby::ascii8bit_encoding` instead")
330    )]
331    #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
332    #[inline]
333    pub fn ascii8bit() -> Self {
334        get_ruby!().ascii8bit_encoding()
335    }
336
337    /// Returns the encoding that represents UTF-8.
338    ///
339    /// # Panics
340    ///
341    /// Panics if called from a non-Ruby thread. See [`Ruby::utf8_encoding`]
342    /// for the non-panicking version.
343    #[cfg_attr(
344        not(feature = "old-api"),
345        deprecated(note = "please use `Ruby::utf8_encoding` instead")
346    )]
347    #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
348    #[inline]
349    pub fn utf8() -> Self {
350        get_ruby!().utf8_encoding()
351    }
352
353    /// Returns the encoding that represents US-ASCII.
354    ///
355    /// # Panics
356    ///
357    /// Panics if called from a non-Ruby thread. See [`Ruby::usascii_encoding`]
358    /// for the non-panicking version.
359    #[cfg_attr(
360        not(feature = "old-api"),
361        deprecated(note = "please use `Ruby::usascii_encoding` instead")
362    )]
363    #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
364    #[inline]
365    pub fn usascii() -> Self {
366        get_ruby!().usascii_encoding()
367    }
368
369    /// Returns the encoding that represents the process' current locale.
370    ///
371    /// This is dynamic. If you change the process' locale that should also
372    /// change the return value of this function.
373    ///
374    /// # Panics
375    ///
376    /// Panics if called from a non-Ruby thread. See [`Ruby::locale_encoding`]
377    /// for the non-panicking version.
378    #[cfg_attr(
379        not(feature = "old-api"),
380        deprecated(note = "please use `Ruby::locale_encoding` instead")
381    )]
382    #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
383    #[inline]
384    pub fn locale() -> Self {
385        get_ruby!().locale_encoding()
386    }
387
388    /// Returns the filesystem encoding.
389    ///
390    /// This is the encoding that Ruby expects data from the OS' file system
391    /// to be encoded as, such as directory names.
392    ///
393    /// # Panics
394    ///
395    /// Panics if called from a non-Ruby thread. See
396    /// [`Ruby::filesystem_encoding`] for the non-panicking version.
397    #[cfg_attr(
398        not(feature = "old-api"),
399        deprecated(note = "please use `Ruby::filesystem_encoding` instead")
400    )]
401    #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
402    #[inline]
403    pub fn filesystem() -> Self {
404        get_ruby!().filesystem_encoding()
405    }
406
407    /// Returns the default external encoding.
408    ///
409    /// This is the encoding used for anything out-of-process, such as reading
410    /// from files or sockets.
411    ///
412    /// # Panics
413    ///
414    /// Panics if called from a non-Ruby thread. See
415    /// [`Ruby::default_external_encoding`] for the non-panicking version.
416    #[cfg_attr(
417        not(feature = "old-api"),
418        deprecated(note = "please use `Ruby::default_external_encoding` instead")
419    )]
420    #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
421    #[inline]
422    pub fn default_external() -> Self {
423        get_ruby!().default_external_encoding()
424    }
425
426    /// Returns the default internal encoding.
427    ///
428    /// If set, any out-of-process data is transcoded from the default external
429    /// encoding to the default internal encoding.
430    ///
431    /// # Panics
432    ///
433    /// Panics if called from a non-Ruby thread. See
434    /// [`Ruby::default_internal_encoding`] for the non-panicking version.
435    #[cfg_attr(
436        not(feature = "old-api"),
437        deprecated(note = "please use `Ruby::default_internal_encoding` instead")
438    )]
439    #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
440    #[inline]
441    pub fn default_internal() -> Option<Self> {
442        get_ruby!().default_internal_encoding()
443    }
444
445    /// Returns the encoding with the name or alias `name`.
446    ///
447    /// # Panics
448    ///
449    /// Panics if called from a non-Ruby thread. See [`Ruby::find_encoding`]
450    /// for the non-panicking version.
451    ///
452    /// # Examples
453    ///
454    /// ```
455    /// # #![allow(deprecated)]
456    /// use magnus::encoding::RbEncoding;
457    /// # let _cleanup = unsafe { magnus::embed::init() };
458    ///
459    /// assert_eq!(RbEncoding::find("UTF-8").unwrap().name(), "UTF-8");
460    /// assert_eq!(RbEncoding::find("BINARY").unwrap().name(), "ASCII-8BIT");
461    /// ```
462    #[cfg_attr(
463        not(feature = "old-api"),
464        deprecated(note = "please use `Ruby::find_encoding` instead")
465    )]
466    #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
467    #[inline]
468    pub fn find(name: &str) -> Option<Self> {
469        get_ruby!().find_encoding(name)
470    }
471
472    pub(crate) fn as_ptr(&self) -> *mut rb_encoding {
473        self.0.as_ptr()
474    }
475
476    /// Returns the canonical name of the encoding.
477    ///
478    /// # Examples
479    ///
480    /// ```
481    /// use magnus::{Error, Ruby};
482    ///
483    /// fn example(ruby: &Ruby) -> Result<(), Error> {
484    ///     assert_eq!(ruby.utf8_encoding().name(), "UTF-8");
485    ///     assert_eq!(ruby.find_encoding("UTF-16").unwrap().name(), "UTF-16");
486    ///
487    ///     Ok(())
488    /// }
489    /// # Ruby::init(example).unwrap()
490    /// ```
491    ///
492    /// # Panics
493    ///
494    /// Panics if the name is not valid UTF-8. Encoding names are expected to
495    /// be ASCII only.
496    pub fn name(&self) -> &str {
497        unsafe { CStr::from_ptr(self.0.as_ref().name).to_str().unwrap() }
498    }
499
500    /// Returns the minimum number of bytes the encoding needs to represent a
501    /// single character.
502    ///
503    /// # Examples
504    ///
505    /// ```
506    /// use magnus::{Error, Ruby};
507    ///
508    /// fn example(ruby: &Ruby) -> Result<(), Error> {
509    ///     assert_eq!(ruby.usascii_encoding().mbminlen(), 1);
510    ///     assert_eq!(ruby.utf8_encoding().mbminlen(), 1);
511    ///
512    ///     Ok(())
513    /// }
514    /// # Ruby::init(example).unwrap()
515    /// ```
516    pub fn mbminlen(&self) -> usize {
517        unsafe { self.0.as_ref().min_enc_len as usize }
518    }
519
520    /// Returns the maximum number of bytes the encoding may need to represent
521    /// a single character.
522    ///
523    /// # Examples
524    ///
525    /// ```
526    /// use magnus::{Error, Ruby};
527    ///
528    /// fn example(ruby: &Ruby) -> Result<(), Error> {
529    ///     assert_eq!(ruby.usascii_encoding().mbmaxlen(), 1);
530    ///     assert_eq!(ruby.utf8_encoding().mbmaxlen(), 4);
531    ///
532    ///     Ok(())
533    /// }
534    /// # Ruby::init(example).unwrap()
535    /// ```
536    pub fn mbmaxlen(&self) -> usize {
537        unsafe { self.0.as_ref().max_enc_len as usize }
538    }
539
540    /// Returns the number of bytes of the first character in `slice`.
541    ///
542    /// If the first byte of `slice` is mid way through a character this will
543    /// return the number of bytes until the next character boundry.
544    ///
545    /// If the slice ends before the last byte of the character this will
546    /// return the number of bytes until the end of the slice.
547    ///
548    /// See also [`fast_mbclen`](RbEncoding::fast_mbclen) and
549    /// [`precise_mbclen`](RbEncoding::precise_mbclen).
550    ///
551    /// # Examples
552    ///
553    /// ```
554    /// use magnus::{
555    ///     encoding::{EncodingCapable, RbEncoding},
556    ///     Error, Ruby,
557    /// };
558    ///
559    /// fn example(ruby: &Ruby) -> Result<(), Error> {
560    ///     let s = ruby.str_new("🦀 café");
561    ///     let encoding: RbEncoding = s.enc_get().into();
562    ///     let mut chars = 0;
563    ///
564    ///     unsafe {
565    ///         let mut bytes = s.as_slice();
566    ///         assert_eq!(bytes.len(), 10);
567    ///
568    ///         while !bytes.is_empty() {
569    ///             chars += 1;
570    ///             let len = encoding.mbclen(bytes);
571    ///             bytes = &bytes[len..];
572    ///         }
573    ///     }
574    ///
575    ///     assert_eq!(chars, 6);
576    ///
577    ///     Ok(())
578    /// }
579    /// # Ruby::init(example).unwrap()
580    /// ```
581    pub fn mbclen(&self, slice: &[u8]) -> usize {
582        let Range { start: p, end: e } = slice.as_ptr_range();
583        unsafe { rb_enc_mbclen(p as *const c_char, e as *const c_char, self.as_ptr()) as usize }
584    }
585
586    /// Returns the number of bytes of the first character in `slice`.
587    ///
588    /// If the first byte of `slice` is mid way through a character this will
589    /// return the number of bytes until the next character boundary.
590    ///
591    /// If the slice ends before the last byte of the character this will
592    /// return the theoretical number of bytes until the end of the character,
593    /// which will be past the end of the slice. If the string has been read
594    /// from an IO source this may indicate more data needs to be read.
595    ///
596    /// See also [`mbclen`](RbEncoding::mbclen) and
597    /// [`precise_mbclen`](RbEncoding::precise_mbclen).
598    ///
599    /// # Examples
600    ///
601    /// ```
602    /// use magnus::{
603    ///     encoding::{EncodingCapable, RbEncoding},
604    ///     Error, Ruby,
605    /// };
606    ///
607    /// fn example(ruby: &Ruby) -> Result<(), Error> {
608    ///     let s = ruby.str_new("🦀 café");
609    ///     let encoding: RbEncoding = s.enc_get().into();
610    ///     let mut chars = 0;
611    ///
612    ///     unsafe {
613    ///         let mut bytes = s.as_slice();
614    ///         assert_eq!(bytes.len(), 10);
615    ///
616    ///         while !bytes.is_empty() {
617    ///             chars += 1;
618    ///             let len = encoding.fast_mbclen(bytes);
619    ///             bytes = &bytes[len..];
620    ///         }
621    ///     }
622    ///
623    ///     assert_eq!(chars, 6);
624    ///
625    ///     Ok(())
626    /// }
627    /// # Ruby::init(example).unwrap()
628    /// ```
629    pub fn fast_mbclen(&self, slice: &[u8]) -> usize {
630        let Range { start: p, end: e } = slice.as_ptr_range();
631        unsafe {
632            rb_enc_fast_mbclen(p as *const c_char, e as *const c_char, self.as_ptr()) as usize
633        }
634    }
635
636    /// Returns the number of bytes of the first character in `slice`.
637    ///
638    /// See also [`mbclen`](RbEncoding::mbclen) and
639    /// [`fast_mbclen`](RbEncoding::fast_mbclen).
640    ///
641    /// # Examples
642    ///
643    /// ```
644    /// use magnus::{
645    ///     encoding::{EncodingCapable, MbcLen, RbEncoding},
646    ///     Error, Ruby,
647    /// };
648    ///
649    /// fn example(ruby: &Ruby) -> Result<(), Error> {
650    ///     let s = ruby.str_new("🦀 café");
651    ///     let encoding: RbEncoding = s.enc_get().into();
652    ///     let mut chars = 0;
653    ///
654    ///     unsafe {
655    ///         let mut bytes = s.as_slice();
656    ///         assert_eq!(bytes.len(), 10);
657    ///
658    ///         while !bytes.is_empty() {
659    ///             chars += 1;
660    ///             match encoding.precise_mbclen(bytes) {
661    ///                 MbcLen::CharFound(len) => bytes = &bytes[len..],
662    ///                 MbcLen::NeedMore(len) => panic!("Met end of string expecting {} bytes", len),
663    ///                 MbcLen::Invalid => panic!("corrupted string"),
664    ///             }
665    ///         }
666    ///     }
667    ///
668    ///     assert_eq!(chars, 6);
669    ///
670    ///     Ok(())
671    /// }
672    /// # Ruby::init(example).unwrap()
673    /// ```
674    pub fn precise_mbclen(&self, slice: &[u8]) -> MbcLen {
675        let Range { start: p, end: e } = slice.as_ptr_range();
676        let r =
677            unsafe { rb_enc_precise_mbclen(p as *const c_char, e as *const c_char, self.as_ptr()) };
678        if 0 < r {
679            MbcLen::CharFound(r as usize)
680        } else if r < -1 {
681            MbcLen::NeedMore((-1 - r) as usize)
682        } else if r == -1 {
683            MbcLen::Invalid
684        } else {
685            unreachable!()
686        }
687    }
688
689    /// If the first character in `slice` is included in ASCII return it and
690    /// its encoded length in `slice`, otherwise returns None.
691    ///
692    /// Typically the length will be 1, but some encodings such as UTF-16 will
693    /// encode ASCII characters in 2 bytes.
694    ///
695    /// # Examples
696    ///
697    /// ```
698    /// use magnus::{
699    ///     encoding::{EncodingCapable, RbEncoding},
700    ///     Error, Ruby,
701    /// };
702    ///
703    /// fn example(ruby: &Ruby) -> Result<(), Error> {
704    ///     let s = ruby.str_new("example");
705    ///     let encoding: RbEncoding = s.enc_get().into();
706    ///     let mut chars = Vec::new();
707    ///
708    ///     unsafe {
709    ///         let mut bytes = s.as_slice();
710    ///
711    ///         while !bytes.is_empty() {
712    ///             match encoding.ascget(bytes) {
713    ///                 Some((char, len)) => {
714    ///                     chars.push(char);
715    ///                     bytes = &bytes[len..];
716    ///                 }
717    ///                 None => panic!("string not ASCII"),
718    ///             }
719    ///         }
720    ///     }
721    ///
722    ///     assert_eq!(chars, [101, 120, 97, 109, 112, 108, 101]);
723    ///
724    ///     Ok(())
725    /// }
726    /// # Ruby::init(example).unwrap()
727    /// ```
728    pub fn ascget(&self, slice: &[u8]) -> Option<(u8, usize)> {
729        let Range { start: p, end: e } = slice.as_ptr_range();
730        let mut len = 0;
731        let c = unsafe {
732            rb_enc_ascget(
733                p as *const c_char,
734                e as *const c_char,
735                &mut len as *mut _,
736                self.as_ptr(),
737            )
738        };
739        if len == 0 {
740            panic!("{:?}", slice);
741        }
742        (c > -1).then_some((c as u8, len as usize))
743    }
744
745    /// Returns the codepoint and length in bytes of the first character in
746    /// `slice`.
747    ///
748    /// # Examples
749    ///
750    /// ```
751    /// use magnus::{
752    ///     encoding::{EncodingCapable, RbEncoding},
753    ///     Error, Ruby,
754    /// };
755    ///
756    /// fn example(ruby: &Ruby) -> Result<(), Error> {
757    ///     let s = ruby.str_new("🦀 café");
758    ///     let encoding: RbEncoding = s.enc_get().into();
759    ///     let mut codepoints = Vec::new();
760    ///
761    ///     unsafe {
762    ///         let mut bytes = s.as_slice();
763    ///
764    ///         while !bytes.is_empty() {
765    ///             let (codepoint, len) = encoding.codepoint_len(bytes)?;
766    ///             codepoints.push(codepoint);
767    ///             bytes = &bytes[len..];
768    ///         }
769    ///     }
770    ///
771    ///     assert_eq!(codepoints, [129408, 32, 99, 97, 102, 233]);
772    ///
773    ///     Ok(())
774    /// }
775    /// # Ruby::init(example).unwrap()
776    /// ```
777    pub fn codepoint_len(&self, slice: &[u8]) -> Result<(u32, usize), Error> {
778        let Range { start: p, end: e } = slice.as_ptr_range();
779        let mut len = 0;
780        let mut c = 0;
781        protect(|| unsafe {
782            c = rb_enc_codepoint_len(
783                p as *const c_char,
784                e as *const c_char,
785                &mut len as *mut _,
786                self.as_ptr(),
787            );
788            Ruby::get_unchecked().qnil()
789        })?;
790        Ok((c, len as usize))
791    }
792
793    /// Returns the number of bytes required to represent the code point `code`
794    /// in the encoding of `self`.
795    ///
796    /// # Examples
797    ///
798    /// ```
799    /// use magnus::{Error, Ruby};
800    ///
801    /// fn example(ruby: &Ruby) -> Result<(), Error> {
802    ///     assert_eq!(ruby.utf8_encoding().codelen(97)?, 1);
803    ///     assert_eq!(ruby.utf8_encoding().codelen(129408)?, 4);
804    ///
805    ///     Ok(())
806    /// }
807    /// # Ruby::init(example).unwrap()
808    /// ```
809    pub fn codelen(&self, code: u32) -> Result<usize, Error> {
810        let handle = unsafe { Ruby::get_unchecked() };
811        let code = code
812            .try_into()
813            .map_err(|e: <usize as TryInto<c_int>>::Error| {
814                Error::new(handle.exception_arg_error(), e.to_string())
815            })?;
816        let mut len = 0;
817        protect(|| {
818            unsafe { len = rb_enc_codelen(code, self.as_ptr()) as usize };
819            handle.qnil()
820        })?;
821        Ok(len)
822    }
823
824    /// Encode the codepoint `code` as a series of bytes in the encoding `self`
825    /// and return the result as a Ruby string.
826    ///
827    /// # Examples
828    ///
829    /// ```
830    /// use magnus::{eval, Error, Ruby};
831    ///
832    /// fn example(ruby: &Ruby) -> Result<(), Error> {
833    ///     let c = ruby.usascii_encoding().chr(97)?;
834    ///     let res: bool = eval!(ruby, r#"c == "a""#, c)?;
835    ///     assert!(res);
836    ///
837    ///     Ok(())
838    /// }
839    /// # Ruby::init(example).unwrap()
840    /// ```
841    ///
842    /// ```
843    /// use magnus::{eval, Error, Ruby};
844    ///
845    /// fn example(ruby: &Ruby) -> Result<(), Error> {
846    ///     let c = ruby.utf8_encoding().chr(129408)?;
847    ///     let res: bool = eval!(ruby, r#"c == "🦀""#, c)?;
848    ///     assert!(res);
849    ///
850    ///     Ok(())
851    /// }
852    /// # Ruby::init(example).unwrap()
853    /// ```
854    pub fn chr(&self, code: u32) -> Result<RString, Error> {
855        protect(|| unsafe {
856            RString::from_rb_value_unchecked(rb_enc_uint_chr(code, self.as_ptr()))
857        })
858    }
859
860    /// Returns `true` if the first character in `slice` is a newline in the
861    /// encoding `self`, `false` otherwise.
862    ///
863    /// # Examples
864    ///
865    /// ```
866    /// use magnus::{Error, Ruby};
867    ///
868    /// fn example(ruby: &Ruby) -> Result<(), Error> {
869    ///     assert!(ruby.utf8_encoding().is_mbc_newline(&[10]));
870    ///     assert!(!ruby.utf8_encoding().is_mbc_newline(&[32]));
871    ///
872    ///     Ok(())
873    /// }
874    /// # Ruby::init(example).unwrap()
875    /// ```
876    pub fn is_mbc_newline(&self, slice: &[u8]) -> bool {
877        let Range { start: p, end: e } = slice.as_ptr_range();
878        unsafe {
879            self.0.as_ref().is_mbc_newline.unwrap()(p as *const _, e as *const _, self.as_ptr())
880                != 0
881        }
882    }
883
884    /// Returns whether the given codepoint `code` is of the character type
885    /// `ctype` in the encoding `self`.
886    ///
887    /// # Examples
888    ///
889    /// ```
890    /// use magnus::{encoding::CType, Error, Ruby};
891    ///
892    /// fn example(ruby: &Ruby) -> Result<(), Error> {
893    ///     assert!(ruby.utf8_encoding().is_code_ctype(9, CType::Space)); // "\t"
894    ///     assert!(ruby.utf8_encoding().is_code_ctype(32, CType::Space)); // " "
895    ///     assert!(!ruby.utf8_encoding().is_code_ctype(65, CType::Space)); // "A"
896    ///     assert!(ruby.utf8_encoding().is_code_ctype(65, CType::Alnum)); // "A"
897    ///     assert!(ruby.utf8_encoding().is_code_ctype(65, CType::Upper)); // "A"
898    ///
899    ///     Ok(())
900    /// }
901    /// # Ruby::init(example).unwrap()
902    /// ```
903    pub fn is_code_ctype(&self, code: u32, ctype: CType) -> bool {
904        unsafe { self.0.as_ref().is_code_ctype.unwrap()(code, ctype as _, self.as_ptr()) != 0 }
905    }
906}
907
908/// Return value for [`RbEncoding::precise_mbclen`].
909pub enum MbcLen {
910    /// Found a valid char, value is the char's length.
911    CharFound(usize),
912    /// The slice ended before the end of the current char. Value is the
913    /// theoretical total length of the char.
914    NeedMore(usize),
915    /// The bytes at the start of the slice are not valid for the encoding.
916    Invalid,
917}
918
919/// A character type.
920#[repr(u32)]
921#[derive(Debug, Copy, Clone)]
922pub enum CType {
923    /// Newline.
924    Newline = 0,
925    /// Alphabetical.
926    Alpha = 1,
927    /// Blank.
928    Blank = 2,
929    /// Control.
930    Cntrl = 3,
931    /// Digit.
932    Digit = 4,
933    /// Graph.
934    Graph = 5,
935    /// Lowercase.
936    Lower = 6,
937    /// Printable.
938    Print = 7,
939    /// Punctuation.
940    Punct = 8,
941    /// Whitespace.
942    Space = 9,
943    /// Uppercase.
944    Upper = 10,
945    /// Xdigit.
946    Xdigit = 11,
947    /// Word.
948    Word = 12,
949    /// Alphanumeric.
950    Alnum = 13,
951    /// ASCII.
952    Ascii = 14,
953}
954
955impl From<RbEncoding> for Encoding {
956    fn from(val: RbEncoding) -> Self {
957        Encoding::from_value(Value::new(unsafe { rb_enc_from_encoding(val.as_ptr()) })).unwrap()
958    }
959}
960
961impl From<RbEncoding> for Index {
962    fn from(val: RbEncoding) -> Self {
963        Index(unsafe { rb_enc_to_index(val.as_ptr()) })
964    }
965}
966
967impl IntoValue for RbEncoding {
968    #[inline]
969    fn into_value_with(self, handle: &Ruby) -> Value {
970        Encoding::from(self).into_value_with(handle)
971    }
972}
973
974impl TryConvert for RbEncoding {
975    fn try_convert(val: Value) -> Result<Self, Error> {
976        let mut ptr = ptr::null_mut();
977        protect(|| unsafe {
978            ptr = rb_to_encoding(val.as_rb_value());
979            Ruby::get_unchecked().qnil()
980        })?;
981        Ok(Self::new(ptr).unwrap())
982    }
983}
984
985/// # Encoding Index
986///
987/// Functions to access pre-defined encodings.
988///
989/// See also the [`encoding::Index`](Index) type.
990impl Ruby {
991    /// Returns the index for ASCII-8BIT a.k.a. binary.
992    pub fn ascii8bit_encindex(&self) -> Index {
993        Index(unsafe { rb_ascii8bit_encindex() })
994    }
995
996    /// Returns the index for UTF-8.
997    pub fn utf8_encindex(&self) -> Index {
998        Index(unsafe { rb_utf8_encindex() })
999    }
1000
1001    /// Returns the index for US-ASCII.
1002    pub fn usascii_encindex(&self) -> Index {
1003        Index(unsafe { rb_usascii_encindex() })
1004    }
1005
1006    /// Returns the index for the process' current locale encoding.
1007    ///
1008    /// This is dynamic. If you change the process' locale that should also
1009    /// change the return value of this function.
1010    pub fn locale_encindex(&self) -> Index {
1011        Index(unsafe { rb_locale_encindex() })
1012    }
1013
1014    /// Returns the index for filesystem encoding.
1015    ///
1016    /// This is the encoding that Ruby expects data from the OS' file system
1017    /// to be encoded as, such as directory names.
1018    pub fn filesystem_encindex(&self) -> Index {
1019        Index(unsafe { rb_filesystem_encindex() })
1020    }
1021
1022    /// Returns the index for the encoding with the name or alias `name`.
1023    ///
1024    /// # Examples
1025    ///
1026    /// ```
1027    /// use magnus::{Error, Ruby};
1028    ///
1029    /// fn example(ruby: &Ruby) -> Result<(), Error> {
1030    ///     assert!(ruby.find_encindex("UTF-8").is_ok());
1031    ///     assert!(ruby.find_encindex("BINARY").is_ok());
1032    ///     assert!(ruby.find_encindex("none").is_err());
1033    ///     Ok(())
1034    /// }
1035    /// # Ruby::init(example).unwrap()
1036    /// ```
1037    pub fn find_encindex(&self, name: &str) -> Result<Index, Error> {
1038        let name = CString::new(name).unwrap();
1039        let mut i = 0;
1040        protect(|| {
1041            unsafe { i = rb_enc_find_index(name.as_ptr()) };
1042            self.qnil()
1043        })?;
1044        if i == -1 {
1045            return Err(Error::new(
1046                self.exception_runtime_error(),
1047                format!("Encoding {:?} exists, but can not be loaded", name),
1048            ));
1049        }
1050        Ok(Index(i))
1051    }
1052}
1053
1054/// The index of an encoding in Ruby's internal encodings table.
1055///
1056/// This is the type Ruby uses to label encoding capable types, so is used with
1057/// operations that require reading or setting that label.
1058///
1059/// See [`Ruby`](Ruby#encoding-index) for methods to get an `encoding::Index`.
1060#[derive(Clone, Copy, Eq, PartialEq)]
1061#[repr(transparent)]
1062pub struct Index(c_int);
1063
1064impl Index {
1065    /// Returns the index for ASCII-8BIT a.k.a. binary.
1066    ///
1067    /// # Panics
1068    ///
1069    /// Panics if called from a non-Ruby thread. See
1070    /// [`Ruby::ascii8bit_encindex`] for the non-panicking version.
1071    #[cfg_attr(
1072        not(feature = "old-api"),
1073        deprecated(note = "please use `Ruby::ascii8bit_encindex` instead")
1074    )]
1075    #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
1076    #[inline]
1077    pub fn ascii8bit() -> Self {
1078        get_ruby!().ascii8bit_encindex()
1079    }
1080
1081    /// Returns the index for UTF-8.
1082    ///
1083    /// # Panics
1084    ///
1085    /// Panics if called from a non-Ruby thread. See [`Ruby::utf8_encindex`]
1086    /// for the non-panicking version.
1087    #[cfg_attr(
1088        not(feature = "old-api"),
1089        deprecated(note = "please use `Ruby::utf8_encindex` instead")
1090    )]
1091    #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
1092    #[inline]
1093    pub fn utf8() -> Self {
1094        get_ruby!().utf8_encindex()
1095    }
1096
1097    /// Returns the index for US-ASCII.
1098    ///
1099    /// # Panics
1100    ///
1101    /// Panics if called from a non-Ruby thread. See [`Ruby::usascii_encindex`]
1102    /// for the non-panicking version.
1103    #[cfg_attr(
1104        not(feature = "old-api"),
1105        deprecated(note = "please use `Ruby::usascii_encindex` instead")
1106    )]
1107    #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
1108    #[inline]
1109    pub fn usascii() -> Self {
1110        get_ruby!().usascii_encindex()
1111    }
1112
1113    /// Returns the index for the process' current locale encoding.
1114    ///
1115    /// This is dynamic. If you change the process' locale that should also
1116    /// change the return value of this function.
1117    ///
1118    /// # Panics
1119    ///
1120    /// Panics if called from a non-Ruby thread. See [`Ruby::locale_encindex`]
1121    /// for the non-panicking version.
1122    #[cfg_attr(
1123        not(feature = "old-api"),
1124        deprecated(note = "please use `Ruby::locale_encindex` instead")
1125    )]
1126    #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
1127    #[inline]
1128    pub fn locale() -> Self {
1129        get_ruby!().locale_encindex()
1130    }
1131
1132    /// Returns the index for filesystem encoding.
1133    ///
1134    /// This is the encoding that Ruby expects data from the OS' file system
1135    /// to be encoded as, such as directory names.
1136    ///
1137    /// # Panics
1138    ///
1139    /// Panics if called from a non-Ruby thread. See
1140    /// [`Ruby::filesystem_encindex`] for the non-panicking version.
1141    #[cfg_attr(
1142        not(feature = "old-api"),
1143        deprecated(note = "please use `Ruby::filesystem_encindex` instead")
1144    )]
1145    #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
1146    #[inline]
1147    pub fn filesystem() -> Self {
1148        get_ruby!().filesystem_encindex()
1149    }
1150
1151    /// Returns the index for the encoding with the name or alias `name`.
1152    ///
1153    /// # Panics
1154    ///
1155    /// Panics if called from a non-Ruby thread. See [`Ruby::find_encindex`]
1156    /// for the non-panicking version.
1157    ///
1158    /// # Examples
1159    ///
1160    /// ```
1161    /// # #![allow(deprecated)]
1162    /// use magnus::encoding;
1163    /// # let _cleanup = unsafe { magnus::embed::init() };
1164    ///
1165    /// assert!(encoding::Index::find("UTF-8").is_ok());
1166    /// assert!(encoding::Index::find("BINARY").is_ok());
1167    /// assert!(encoding::Index::find("none").is_err());
1168    /// ```
1169    #[cfg_attr(
1170        not(feature = "old-api"),
1171        deprecated(note = "please use `Ruby::find_encindex` instead")
1172    )]
1173    #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
1174    #[inline]
1175    pub fn find(name: &str) -> Result<Self, Error> {
1176        get_ruby!().find_encindex(name)
1177    }
1178
1179    pub(crate) fn to_int(self) -> c_int {
1180        self.0
1181    }
1182}
1183
1184impl From<Index> for RbEncoding {
1185    fn from(val: Index) -> Self {
1186        RbEncoding::new(unsafe { rb_enc_from_index(val.to_int()) }).expect("no encoding for index")
1187    }
1188}
1189
1190impl TryConvert for Index {
1191    fn try_convert(val: Value) -> Result<Self, Error> {
1192        let i = unsafe { rb_to_encoding_index(val.as_rb_value()) };
1193        if i == -1 && RString::from_value(val).is_some() {
1194            return Err(Error::new(
1195                Ruby::get_with(val).exception_runtime_error(),
1196                format!("ArgumentError: unknown encoding name - {}", val),
1197            ));
1198        } else if i == -1 {
1199            return TryConvert::try_convert(RString::try_convert(val)?.as_value());
1200        }
1201        Ok(Index(i))
1202    }
1203}
1204
1205/// Possible states of how a string matches its encoding.
1206#[repr(u32)]
1207#[derive(Debug, Copy, Clone, Eq, PartialEq)]
1208pub enum Coderange {
1209    /// It is unknown if the string is valid for its encoding.
1210    Unknown = 0,
1211    /// The string is entirely within the 0 to 127 ASCII range.
1212    SevenBit = 1048576,
1213    /// The string is valid for its encoding.
1214    Valid = 2097152,
1215    /// The string holds values that are invalid for its encoding.
1216    Broken = 3145728,
1217}
1218
1219/// Trait that marks Ruby types cable of having an encoding.
1220pub trait EncodingCapable: ReprValue + Copy {
1221    /// Get the encoding of `self`.
1222    ///
1223    /// # Examples
1224    ///
1225    /// ```
1226    /// use magnus::{encoding::EncodingCapable, Error, Ruby};
1227    ///
1228    /// fn example(ruby: &Ruby) -> Result<(), Error> {
1229    ///     assert!(ruby.str_new("example").enc_get() == ruby.utf8_encindex());
1230    ///
1231    ///     Ok(())
1232    /// }
1233    /// # Ruby::init(example).unwrap()
1234    /// ```
1235    fn enc_get(self) -> Index {
1236        let i = unsafe { rb_enc_get_index(self.as_rb_value()) };
1237        if i == -1 {
1238            panic!("{} not encoding capable", self.as_value());
1239        }
1240        Index(i)
1241    }
1242
1243    /// Set `self`'s encoding.
1244    ///
1245    /// Returns `Err` if `self` is frozen or the encoding can not be loaded.
1246    ///
1247    /// See also [`EncodingCapable::enc_associate`].
1248    ///
1249    /// # Examples
1250    ///
1251    /// ```
1252    /// use magnus::{encoding::EncodingCapable, Error, Ruby};
1253    ///
1254    /// fn example(ruby: &Ruby) -> Result<(), Error> {
1255    ///     let s = ruby.str_new("example");
1256    ///     assert!(s.enc_get() == ruby.utf8_encindex());
1257    ///     s.enc_set(ruby.usascii_encindex())?;
1258    ///     assert!(s.enc_get() == ruby.usascii_encindex());
1259    ///
1260    ///     Ok(())
1261    /// }
1262    /// # Ruby::init(example).unwrap()
1263    /// ```
1264    fn enc_set<T>(self, enc: T) -> Result<(), Error>
1265    where
1266        T: Into<Index>,
1267    {
1268        protect(|| unsafe {
1269            rb_enc_set_index(self.as_rb_value(), enc.into().to_int());
1270            Ruby::get_unchecked().qnil()
1271        })?;
1272        Ok(())
1273    }
1274
1275    /// Set `self`'s encoding, along with performing additional fix-up `self`'s
1276    /// contents.
1277    ///
1278    /// For example, Ruby's strings contain an additional terminating null byte
1279    /// hidden from Ruby, but allowing for easy c string interop. This method
1280    /// will adjust the length of that terminating char depending on the
1281    /// encoding.
1282    ///
1283    /// Returns `Err` if `self` is frozen or the encoding can not be loaded.
1284    ///
1285    /// # Examples
1286    ///
1287    /// ```
1288    /// use magnus::{encoding::EncodingCapable, Error, Ruby};
1289    ///
1290    /// fn example(ruby: &Ruby) -> Result<(), Error> {
1291    ///     let s = ruby.str_new("example");
1292    ///     assert!(s.enc_get() == ruby.utf8_encindex());
1293    ///     s.enc_associate(ruby.usascii_encindex())?;
1294    ///     assert!(s.enc_get() == ruby.usascii_encindex());
1295    ///
1296    ///     Ok(())
1297    /// }
1298    /// # Ruby::init(example).unwrap()
1299    /// ```
1300    fn enc_associate<T>(self, enc: T) -> Result<(), Error>
1301    where
1302        T: Into<Index>,
1303    {
1304        protect(|| {
1305            Value::new(unsafe { rb_enc_associate_index(self.as_rb_value(), enc.into().to_int()) })
1306        })?;
1307        Ok(())
1308    }
1309}
1310
1311/// Returns the common encoding between `v1` and `v2`, or `None`.
1312///
1313/// Returns `None` if there is no common compatible encoding.
1314///
1315/// See also [`check`].
1316///
1317/// # Examples
1318///
1319/// ```
1320/// use magnus::{encoding, prelude::*, Error, Ruby};
1321///
1322/// fn example(ruby: &Ruby) -> Result<(), Error> {
1323///     let a = ruby.str_new("a");
1324///     let b = ruby.str_new("b");
1325///
1326///     assert!(a.enc_get() == ruby.utf8_encindex());
1327///     b.enc_set(ruby.usascii_encindex())?;
1328///
1329///     assert_eq!(encoding::compatible(a, b).unwrap().name(), "UTF-8");
1330///
1331///     Ok(())
1332/// }
1333/// # Ruby::init(example).unwrap()
1334/// ```
1335pub fn compatible<T, U>(v1: T, v2: U) -> Option<RbEncoding>
1336where
1337    T: EncodingCapable,
1338    U: EncodingCapable,
1339{
1340    RbEncoding::new(unsafe { rb_enc_compatible(v1.as_rb_value(), v2.as_rb_value()) })
1341}
1342
1343/// Returns the common encoding between `v1` and `v2`, or `Err`.
1344///
1345/// Returns `Err` if there is no common compatible encoding.
1346///
1347/// See also [`compatible`].
1348///
1349/// # Examples
1350///
1351/// ```
1352/// use magnus::{encoding, prelude::*, Error, Ruby};
1353///
1354/// fn example(ruby: &Ruby) -> Result<(), Error> {
1355///     let a = ruby.str_new("a");
1356///     let b = ruby.str_new("b");
1357///
1358///     assert!(a.enc_get() == ruby.utf8_encindex());
1359///     b.enc_set(ruby.usascii_encindex())?;
1360///
1361///     assert_eq!(encoding::check(a, b)?.name(), "UTF-8");
1362///
1363///     Ok(())
1364/// }
1365/// # Ruby::init(example).unwrap()
1366/// ```
1367pub fn check<T, U>(v1: T, v2: U) -> Result<RbEncoding, Error>
1368where
1369    T: EncodingCapable,
1370    U: EncodingCapable,
1371{
1372    let mut ptr = ptr::null_mut();
1373    protect(|| unsafe {
1374        ptr = rb_enc_check(v1.as_rb_value(), v2.as_rb_value());
1375        Ruby::get_with(v1).qnil()
1376    })?;
1377    Ok(RbEncoding::new(ptr).unwrap())
1378}
1379
1380/// Compies the encoding from `src` to `dst`.
1381///
1382/// This does not reconcode `dst.`
1383///
1384/// Similar to [`EncodingCapable::enc_associate`], except takes the encoding of
1385/// `src` rather than an encoding object or index.
1386///
1387/// # Examples
1388///
1389/// ```
1390/// use magnus::{encoding, prelude::*, Error, Ruby};
1391///
1392/// fn example(ruby: &Ruby) -> Result<(), Error> {
1393///     let a = ruby.str_new("a");
1394///     assert!(a.enc_get() == ruby.utf8_encindex());
1395///     let b = ruby.str_new("b");
1396///     assert!(b.enc_get() == ruby.utf8_encindex());
1397///
1398///     a.enc_set(ruby.usascii_encindex())?;
1399///     encoding::copy(b, a)?;
1400///
1401///     assert!(b.enc_get() == ruby.usascii_encindex());
1402///
1403///     Ok(())
1404/// }
1405/// # Ruby::init(example).unwrap()
1406/// ```
1407pub fn copy<T, U>(dst: T, src: U) -> Result<(), Error>
1408where
1409    T: EncodingCapable,
1410    U: EncodingCapable,
1411{
1412    protect(|| unsafe {
1413        rb_enc_copy(dst.as_rb_value(), src.as_rb_value());
1414        Ruby::get_with(dst).qnil()
1415    })?;
1416    Ok(())
1417}