昨天解决了焦点问题,今天就开始搬砖了。本以为可以一帆风顺,但是还是遇到了几个问题,不过还好,都被一一解决,这里我分享一下JsValue的转换体验以及关键字as
的使用浅析。
场景描述
我是在什么情况下遇到JsValue的转换的呢?获取当前窗口的内部宽度和内部高度。api的接口定义如下:
pub fn inner_width(&self) -> Result<JsValue, JsValue>
pub fn inner_height(&self) -> Result<JsValue, JsValue>
我使用当前窗口的内部宽度和内部高度的地方在Modal组件中:
#[derive(Properties, Clone, PartialEq)]
pub struct Props {...#[prop_or(200)]pub width: u32,#[prop_or(500)]pub height: u32,
}
也就是说,我要将JsValue转换成u32类型。
问题解决
先看这个问题最终是怎么解决的吧。和Javascript代码比起来,是稍微复杂了一点点。
pub fn get_window_size() -> Option<(u32, u32)> {if let Some(window) = get_window() {let width = window.inner_width().unwrap_or_else(|_| JsValue::from_f64(default_width1));let height = window.inner_height().unwrap_or_else(|_| JsValue::from_f64(default_height1));let width1: f64 = width.as_f64()?;let height1: f64 = height.as_f64()?;let width2 = width1 as u32;let height2 = height1 as u32;return Some((width2, height2));}None
}
逻辑比较简单,步骤如下
- 先通过window对象拿到
width
和height
; - 通过方法
as_f64
将JsValue转换成f64
类型; - 通过rust的
as
关键字将f64
类型转换成u32
类型;
问题展开
从问题的解决层面上来说,明确上面3步,就可以拿到我们需要的数据,但是这显然只是冰山一角。水下面的东西很多,这里我挑2个容易的来说。
关于JsValue的数据转换
JsValue定义在wasm_bindgen
中,关于数据转换的接口有下面这几个:
pub fn as_bool(&self) -> Option<bool>;
pub fn as_f64(&self) -> Option<f64>;
pub fn as_string(&self) -> Option<String>;pub const fn from_bool(b: bool) -> JsValue;
pub fn from_f64(n: f64) -> JsValue;
pub fn from_str(s: &str) -> JsValue;
也就是说,wasm_bindgen::JsValue
中,只关心布尔值,字符串和数字,而数字在rust中使用的是f64,即rust std中支持的容量最大,精度最高的数字。JsValue代表了Javascript环境中的数据。因此,了解这一点,我们就可以自由的进行Javascript和Rust的数据交换了。
关于as
在上面的代码中,我们通过rust的as
关键字,将f64
数字类型转换成了u32
数字类型。
在Rust中,as关键字可以用于类型转换,将一个类型转换为另一个类型,这里的类型必须原始类型(primitive types),但显然不是指所有的原始类型。
什么是原始类型呢?原始类型是Rust语言的基本的数据类型,它们是语言的一部分。Rust语言的原始类型包括一下几种:
布尔类型(bool):表示逻辑值,只能是true或false。
字符类型(char):表示单个Unicode字符。
整数类型(integer):包括有符号整数(i8、i16、i32、i64、isize)和无符号整数(u8、u16、u32、u64、usize)。
浮点数类型(floating-point):包括单精度浮点数(f32)和双精度浮点数(f64)。
元组类型(tuple):可以包含多个不同类型的值。
数组类型(array):包含固定长度的相同类型的值。
切片类型(slice):对数组的引用,可以动态指定长度。
指针类型(pointer):包括原始指针(const T和mut T)和引用(&T)。
在我们的问题场景中,我们用as
将f64
转换成了u32
。难道还能将tuple
转换成u32
不成,显然是不可能的。还好编译器会给出提示。
let num1 = (-33, -5);let num: u32 = num1 as u32;
报错:
3 | let num: u32 = num1 as u32;| ^^^^^^^^^^^ an `as` expression can only be used to convert between primitive types or to coerce to a specific trait object
其实在这里,我用as
关键字是迫不得已的。因为我没有在u32
这个类型上找到impl TryFrom<f64> for u32
这样的方法。那将f64类型的数据转换成u32类型的数据,编译器没有报错,是不是就意味着没有问题呢?在我们的问题场景中,应该是没有问题的。但是从数据本身的角度,是有问题的。
fn main() {let num: f64 = -323.3;let num1: u32 = num as u32;println!("numbers:{}, {}", num, num1);
}
打印结果如下:
numbers:-323.3, 0
我想你应该总结处理这里的规律了吧,再看看下面这段就让你大跌眼镜了:
fn main() {let num: i32 = -3;let num1: u32 = num as u32;println!("numbers:{}, {}", num, num1);
}
按照之前的f64
到u32
的经验,打印的结果应该是:-3, 0,对吧?
错,结果也让我吓一跳:
numbers:-3, 4294967293
而u32
的最大数值是:4294967295。
这里的水有多深,我就不往下说了,我怕淹死在这里,哈哈。
正确的做法是:
fn main() {let num: i32 = -3;let num1: u32 = u32::try_from(num).unwrap_or_else(|_| 0);println!("numbers:{}, {}", num, num1);
}
所以,关于使用as
来转换数据,如果类型本身实现了TryFrom<T> for ...
,那么就不要使用as
来进行类型转换。
总结
在基于wasm_bindgen的WebAssembly开发过程中,JsValue与Rust原始数据类型之间的转换会是家常便饭。对于JsValue来说,它只关心布尔值,字符串和数字,分别对应Rust中的bool
,String
和f64
。f64
与其它Rust数字之间的转换需要依赖as
关键字。但是,如果数字类型实现了TryFrom<T>
这个trait,就不要使用as
关键字来进行转换。
今天就到这里,欢迎大家留言交流。