Skip to content

Commit 1db2f9f

Browse files
committed
Add a JsCast trait specified in [RFC 2]
[RFC 2]: rustwasm/rfcs#2
1 parent e9f9ede commit 1db2f9f

File tree

2 files changed

+184
-0
lines changed

2 files changed

+184
-0
lines changed

src/cast.rs

+165
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
use JsValue;
2+
3+
/// A trait for checked and unchecked casting between JS types.
4+
///
5+
/// Specified [in an RFC][rfc] this trait is intended to provide support for
6+
/// casting JS values between differnet types of one another. In JS there aren't
7+
/// many static types but we've ascribed JS values with static types in Rust,
8+
/// yet they often need to be switched to other types temporarily! This trait
9+
/// provides both checked and unchecked casting into various kinds of values.
10+
///
11+
/// This trait is automatically implemented for any type imported in a
12+
/// `#[wasm_bindgen]` `extern` block.
13+
///
14+
/// [rfc]: https://github.com/rustwasm/rfcs/pull/2
15+
pub trait JsCast
16+
where
17+
Self: AsRef<JsValue> + AsMut<JsValue> + Into<JsValue>,
18+
{
19+
/// Test whether this JS value is an instance of the type `T`.
20+
///
21+
/// This commit performs a dynamic check (at runtime) using the JS
22+
/// `instanceof` operator. This method returns `self instanceof T`.
23+
fn is_instance_of<T>(&self) -> bool
24+
where
25+
T: JsCast,
26+
{
27+
T::instanceof(self.as_ref())
28+
}
29+
30+
/// Performs a dynamic cast (checked at runtime) of this value into the
31+
/// target type `T`.
32+
///
33+
/// This method will return `Err(self)` is `self.is_instance_of::<T>()`
34+
/// returns `false`, and otherwise it will return `Ok(T)` manufactured with
35+
/// an unchecked cast (verified safe via the `instanceof` operation).
36+
fn dyn_into<T>(self) -> Result<T, Self>
37+
where
38+
T: JsCast,
39+
{
40+
if self.is_instance_of::<T>() {
41+
Ok(self.unchecked_into())
42+
} else {
43+
Err(self)
44+
}
45+
}
46+
47+
/// Performs a dynamic cast (checked at runtime) of this value into the
48+
/// target type `T`.
49+
///
50+
/// This method will return `None` is `self.is_instance_of::<T>()`
51+
/// returns `false`, and otherwise it will return `Some(&T)` manufactured
52+
/// with an unchecked cast (verified safe via the `instanceof` operation).
53+
fn dyn_ref<T>(&self) -> Option<&T>
54+
where
55+
T: JsCast,
56+
{
57+
if self.is_instance_of::<T>() {
58+
Some(self.unchecked_ref())
59+
} else {
60+
None
61+
}
62+
}
63+
64+
/// Performs a dynamic cast (checked at runtime) of this value into the
65+
/// target type `T`.
66+
///
67+
/// This method will return `None` is `self.is_instance_of::<T>()`
68+
/// returns `false`, and otherwise it will return `Some(&mut T)`
69+
/// manufactured with an unchecked cast (verified safe via the `instanceof`
70+
/// operation).
71+
fn dyn_mut<T>(&mut self) -> Option<&mut T>
72+
where
73+
T: JsCast,
74+
{
75+
if self.is_instance_of::<T>() {
76+
Some(self.unchecked_mut())
77+
} else {
78+
None
79+
}
80+
}
81+
82+
/// Performs a zero-cost unchecked cast into the specified type.
83+
///
84+
/// This method will convert the `self` value to the type `T`, where both
85+
/// `self` and `T` are simple wrappers around `JsValue`. This method **does
86+
/// not check whether `self` is an instance of `T`**. If used incorrectly
87+
/// then this method may cause runtime exceptions in both Rust and JS, this
88+
/// shoudl be used with caution.
89+
fn unchecked_into<T>(self) -> T
90+
where
91+
T: JsCast,
92+
{
93+
T::unchecked_from_js(self.into())
94+
}
95+
96+
/// Performs a zero-cost unchecked cast into a reference to the specified
97+
/// type.
98+
///
99+
/// This method will convert the `self` value to the type `T`, where both
100+
/// `self` and `T` are simple wrappers around `JsValue`. This method **does
101+
/// not check whether `self` is an instance of `T`**. If used incorrectly
102+
/// then this method may cause runtime exceptions in both Rust and JS, this
103+
/// shoudl be used with caution.
104+
///
105+
/// This method, unlike `unchecked_into`, does not consume ownership of
106+
/// `self` and instead works over a shared reference.
107+
fn unchecked_ref<T>(&self) -> &T
108+
where
109+
T: JsCast,
110+
{
111+
T::unchecked_from_js_ref(self.as_ref())
112+
}
113+
114+
/// Performs a zero-cost unchecked cast into a mutable reference to the
115+
/// specified type.
116+
///
117+
/// This method will convert the `self` value to the type `T`, where both
118+
/// `self` and `T` are simple wrappers around `JsValue`. This method **does
119+
/// not check whether `self` is an instance of `T`**. If used incorrectly
120+
/// then this method may cause runtime exceptions in both Rust and JS, this
121+
/// shoudl be used with caution.
122+
///
123+
/// This method, unlike `unchecked_into`, does not consume ownership of
124+
/// `self` and instead works over a utable reference.
125+
fn unchecked_mut<T>(&mut self) -> &mut T
126+
where
127+
T: JsCast,
128+
{
129+
T::unchecked_from_js_mut(self.as_mut())
130+
}
131+
132+
/// Performs a dynamic `instanceof` check to see whether the `JsValue`
133+
/// provided is an instance of this type.
134+
///
135+
/// This is intended to be an internal implementation detail, you likely
136+
/// won't need to call this.
137+
fn instanceof(val: &JsValue) -> bool;
138+
139+
/// Performs a zero-cost unchecked conversion from a `JsValue` into an
140+
/// instance of `Self`
141+
///
142+
/// This is intended to be an internal implementation detail, you likely
143+
/// won't need to call this.
144+
fn unchecked_from_js(val: JsValue) -> Self;
145+
146+
/// Performs a zero-cost unchecked conversion from a `&JsValue` into an
147+
/// instance of `&Self`.
148+
///
149+
/// Note the safety of this method, which basically means that `Self` must
150+
/// be a newtype wrapper around `JsValue`.
151+
///
152+
/// This is intended to be an internal implementation detail, you likely
153+
/// won't need to call this.
154+
fn unchecked_from_js_ref(val: &JsValue) -> &Self;
155+
156+
/// Performs a zero-cost unchecked conversion from a `&mut JsValue` into an
157+
/// instance of `&mut Self`.
158+
///
159+
/// Note the safety of this method, which basically means that `Self` must
160+
/// be a newtype wrapper around `JsValue`.
161+
///
162+
/// This is intended to be an internal implementation detail, you likely
163+
/// won't need to call this.
164+
fn unchecked_from_js_mut(val: &mut JsValue) -> &mut Self;
165+
}

src/lib.rs

+19
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ pub mod prelude {
4646
pub mod convert;
4747
pub mod describe;
4848

49+
mod cast;
50+
pub use cast::JsCast;
51+
4952
if_std! {
5053
extern crate std;
5154
use std::prelude::v1::*;
@@ -347,6 +350,22 @@ impl From<bool> for JsValue {
347350
}
348351
}
349352

353+
impl JsCast for JsValue {
354+
// everything is a `JsValue`!
355+
fn instanceof(_val: &JsValue) -> bool { true }
356+
fn unchecked_from_js(val: JsValue) -> Self { val }
357+
fn unchecked_from_js_ref(val: &JsValue) -> &Self { val }
358+
fn unchecked_from_js_mut(val: &mut JsValue) -> &mut Self { val }
359+
}
360+
361+
impl AsMut<JsValue> for JsValue {
362+
fn as_mut(&mut self) -> &mut JsValue { self }
363+
}
364+
365+
impl AsRef<JsValue> for JsValue {
366+
fn as_ref(&self) -> &JsValue { self }
367+
}
368+
350369
macro_rules! numbers {
351370
($($n:ident)*) => ($(
352371
impl PartialEq<$n> for JsValue {

0 commit comments

Comments
 (0)