wasm_bindgen/cast.rs
1use crate::{convert::TryFromJsValue, JsValue};
2
3/// A trait for dynamic checked and unchecked casting between JS types.
4///
5/// Unlike [`crate::Upcast`], which provides type-safe zero-cost type
6/// conversions for generic type wrappers, this trait can be used to
7/// perform arbitrary casts with JS instance checking.
8///
9/// Specified [in an RFC][rfc] this trait is intended to provide support for
10/// casting JS values between different types of one another. In JS there aren't
11/// many static types but we've ascribed JS values with static types in Rust,
12/// yet they often need to be switched to other types temporarily! This trait
13/// provides both checked and unchecked casting into various kinds of values.
14///
15/// This trait is automatically implemented for any type imported in a
16/// `#[wasm_bindgen]` `extern` block.
17///
18/// [rfc]: https://github.com/rustwasm/rfcs/blob/master/text/002-wasm-bindgen-inheritance-casting.md
19pub trait JsCast
20where
21 Self: AsRef<JsValue> + Into<JsValue>,
22{
23 /// Test whether this JS value has a type `T`.
24 ///
25 /// This method will dynamically check to see if this JS object can be
26 /// casted to the JS object of type `T`. Usually this uses the `instanceof`
27 /// operator. This also works with primitive types like
28 /// booleans/strings/numbers as well as cross-realm object like `Array`
29 /// which can originate from other iframes.
30 ///
31 /// In general this is intended to be a more robust version of
32 /// `is_instance_of`, but if you want strictly the `instanceof` operator
33 /// it's recommended to use that instead.
34 fn has_type<T>(&self) -> bool
35 where
36 T: JsCast,
37 {
38 T::is_type_of(self.as_ref())
39 }
40
41 /// Performs a dynamic cast (checked at runtime) of this value into the
42 /// target type `T`.
43 ///
44 /// This method will return `Err(self)` if `self.has_type::<T>()`
45 /// returns `false`, and otherwise it will return `Ok(T)` manufactured with
46 /// an unchecked cast (verified correct via the `has_type` operation).
47 fn dyn_into<T>(self) -> Result<T, Self>
48 where
49 T: JsCast,
50 {
51 if self.has_type::<T>() {
52 Ok(self.unchecked_into())
53 } else {
54 Err(self)
55 }
56 }
57
58 /// Performs a dynamic cast (checked at runtime) of this value into the
59 /// target type `T`.
60 ///
61 /// This method will return `None` if `self.has_type::<T>()`
62 /// returns `false`, and otherwise it will return `Some(&T)` manufactured
63 /// with an unchecked cast (verified correct via the `has_type` operation).
64 fn dyn_ref<T>(&self) -> Option<&T>
65 where
66 T: JsCast,
67 {
68 if self.has_type::<T>() {
69 Some(self.unchecked_ref())
70 } else {
71 None
72 }
73 }
74
75 /// Performs a zero-cost unchecked cast into the specified type.
76 ///
77 /// This method will convert the `self` value to the type `T`, where both
78 /// `self` and `T` are simple wrappers around `JsValue`. This method **does
79 /// not check whether `self` is an instance of `T`**. If used incorrectly
80 /// then this method may cause runtime exceptions in both Rust and JS, this
81 /// should be used with caution.
82 fn unchecked_into<T>(self) -> T
83 where
84 T: JsCast,
85 {
86 T::unchecked_from_js(self.into())
87 }
88
89 /// Performs a zero-cost unchecked cast into a reference to the specified
90 /// type.
91 ///
92 /// This method will convert the `self` value to the type `T`, where both
93 /// `self` and `T` are simple wrappers around `JsValue`. This method **does
94 /// not check whether `self` is an instance of `T`**. If used incorrectly
95 /// then this method may cause runtime exceptions in both Rust and JS, this
96 /// should be used with caution.
97 ///
98 /// This method, unlike `unchecked_into`, does not consume ownership of
99 /// `self` and instead works over a shared reference.
100 fn unchecked_ref<T>(&self) -> &T
101 where
102 T: JsCast,
103 {
104 T::unchecked_from_js_ref(self.as_ref())
105 }
106
107 /// Test whether this JS value is an instance of the type `T`.
108 ///
109 /// This method performs a dynamic check (at runtime) using the JS
110 /// `instanceof` operator. This method returns `self instanceof T`.
111 ///
112 /// Note that `instanceof` does not always work with primitive values or
113 /// across different realms (e.g. iframes). If you're not sure whether you
114 /// specifically need only `instanceof` it's recommended to use `has_type`
115 /// instead.
116 fn is_instance_of<T>(&self) -> bool
117 where
118 T: JsCast,
119 {
120 T::instanceof(self.as_ref())
121 }
122
123 /// Performs a dynamic `instanceof` check to see whether the `JsValue`
124 /// provided is an instance of this type.
125 ///
126 /// This is intended to be an internal implementation detail, you likely
127 /// won't need to call this. It's generally called through the
128 /// `is_instance_of` method instead.
129 fn instanceof(val: &JsValue) -> bool;
130
131 /// Performs a dynamic check to see whether the `JsValue` provided
132 /// is a value of this type.
133 ///
134 /// Unlike `instanceof`, this can be specialised to use a custom check by
135 /// adding a `#[wasm_bindgen(is_type_of = callback)]` attribute to the
136 /// type import declaration.
137 ///
138 /// Other than that, this is intended to be an internal implementation
139 /// detail of `has_type` and you likely won't need to call this.
140 fn is_type_of(val: &JsValue) -> bool {
141 Self::instanceof(val)
142 }
143
144 /// Performs a zero-cost unchecked conversion from a `JsValue` into an
145 /// instance of `Self`
146 ///
147 /// This is intended to be an internal implementation detail, you likely
148 /// won't need to call this.
149 fn unchecked_from_js(val: JsValue) -> Self;
150
151 /// Performs a zero-cost unchecked conversion from a `&JsValue` into an
152 /// instance of `&Self`.
153 ///
154 /// Note the safety of this method, which basically means that `Self` must
155 /// be a newtype wrapper around `JsValue`.
156 ///
157 /// This is intended to be an internal implementation detail, you likely
158 /// won't need to call this.
159 fn unchecked_from_js_ref(val: &JsValue) -> &Self;
160}
161
162impl<T: JsCast> TryFromJsValue for T {
163 #[inline]
164 fn try_from_js_value(val: JsValue) -> Result<Self, JsValue> {
165 val.dyn_into()
166 }
167
168 #[inline]
169 fn try_from_js_value_ref(val: &JsValue) -> Option<Self> {
170 val.clone().dyn_into().ok()
171 }
172}