通过之前的内容介绍,对Rust或多或少有了一些了解.也许现在还不能写出“像样子”的项目,但是把大量代码堆积写在一个文件中依旧是不可取的.今天的内容相对轻松一些,聊聊Rust的包和模块
Rust的模块系统可以划分为Package,Crate,Module,具体可以总结如下:
- Package:整个项目
- Crate:编译时的最小代码单位,即可以编译为二进制可执行文件,也可以是库.
- Module:模块,用于解耦逻辑功能代码
Package可以包含任意个二进制Crate,最多只能包含一个library Crate;同时,最少得包含一个Crate,无论是库还是二进制文件.
下面就通过一个例子来简单体会一下这种代码结构组织
首先按照惯例创建一个新的项目,只是这一次创建的不再是常规的二进制而是库cargo new crate-demo --lib
,这样我们就得到了一个Package.
然后配置一下Cargo.toml
,添加依赖以及包类型
[lib]
crate-type=["cdylib"]
[dependencies]
wasm-bindgen="0.2.87"
这里设置crate-type
为cdylib,其实Rust构建lib有很多种type,我们可以通过rustc --help|grep crate-type
进行查看
这里稍作解释
- bin: 二进制可执行文件,编译时有main函数作为入口会自动识别
- lib: 是一种别名,没有具体表示
- rlib: Rust lib静态库(不指定时默认值),用于纯Rust代码之间的依赖调用
- dylib: 动态库,用于纯Rust代码之间调用
- cdylib: C规范动态库,可提供给其他语言调用
- staticlib:静态库,将所有代码以及依赖打包编译
- proc-macro:过程宏,可以被其他crate调用
我们这里依赖wasm-bindgen
相信熟悉的应该都知道了,这个demo是关于WebAssembly的,这也解释了为什么crate-type选择cdylib.构建好Package,我们就可以在crate内编写module,最终实现功能了.
这里我们的函数很简单,就是将i32
的加减乘除封装一下,从而通过不同的flag对应不同的op进行计算.为了展示代码结构化,特意将各个op分开两两一组,最终的文件树如下
这里分别使用ops.rs
,add_mul.rs
,sub_div.rs
导出内部包,内容分别为pub mod xxx
然后在各个子文件夹写算子计算函数,例如
最后lib.rs
中封装一下
use ops::add_mul::{add, mul};
use ops::sub_div::{div, sub};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn op_cal(flag:u8,a:i32,b:i32)->i32{
match flag {
1=>{add::add(a,b)}
2=>{mul::mul(a,b)}
3=>{sub::sub(a,b)}
4=>{div::div(a,b)}
_ => 0
}
}
这里需要导入wasm相关依赖,具体可以去看官网介绍
完成这些,然后安装wasm-pack:cargo install wasm-pack
,安装如果提示需要更新Rust,直接rustup update
,并且为了防止编译报错,在.toml中加上
[package.metadata.wasm-pack.profile.release]
wasm-opt = false
万事俱备开始编译:wasm-pack build --target web
等待一会,编译之后可以看到多出一个pkg文件夹,里面包含了打包好的js,ts等文件.
这里的op_cal就是我们Rust写的lib中封装的函数,然后再写一个前端html调用这个js试试.前端入门级,网上copy了一段代码稍微修改一下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Rust && WebAssembly Demo</title>
</head>
<body>
<table>
<tr>
<td><input type="button" value="add" onclick="setOp('+', 'add');"/></td>
<td><input type="button" value="mul" onclick="setOp('*', 'mul');"/></td>
<td><input type="button" value="sub" onclick="setOp('-', 'sub');"/></td>
<td><input type="button" value="div" onclick="setOp('/', 'div');"/></td>
</tr>
</table>
<table id="tb_calc">
<tr>
<td><input id="x" type="text" value="" name="x" /></td>
<td><lable id="op" name="op"></lable> </td>
<td><input id="y" type="text" value="" name="y" /> </td>
<td><input id="opTips" type="button" value=""/> </td>
<td><lable id="result" name="z" value="?"></lable> </td>
</tr>
</table>
<script type="application/javascript">
function setOp(op, opTips)
{
const tb = document.getElementById("tb_calc");
tb.style.display = "none";
document.getElementById("x").value = "";
document.getElementById("y").value = "";
document.getElementById("result").innerText = "";
document.getElementById("op").innerText = op;
document.getElementById("opTips").value = opTips;
tb.style.display = "block";
}
</script>
<script type="module">
import init,{op_cal} from './pkg/crate_demo.js';
const cal=async ()=>{
await init();
const x = parseInt(document.getElementById("x").value);
const y = parseInt(document.getElementById("y").value);
let op = document.getElementById("op").innerText;
switch (op) {
case '+':
op=1;
break;
case '*':
op=2;
break;
case '-':
op=3;
break;
case '/':
op=4;
break;
}
const result=op_cal(parseInt(op),x,y);
document.getElementById("result").innerText = result.toString();
}
let btn=document.getElementById('opTips');
btn.addEventListener('click',cal,false);
</script>
</body>
</html>
用http-server运行看看效果
基本实现了目标,不过这个html的逻辑属实太烂.明明可以通过点击不同op实现不同计算而不是每次换个op就得重新输入,不过毕竟只是验证wasm打包后调用是否正常做个简单demo,也就没必要刻意优化.
总结
总的来说,Rust的代码组织文件系统还是比较人性化的,而且使用起来也比较容易上手.相比起go没有workspace之前引入本地包来说,这种体验简直不要太好.这里也有很多没有提到的,比如as
别名,*
和self
引入全部以及本身等等,这些其实在其他语言中或多或少都有相似之处,因此慢慢后期用到会稍微介绍一下.最后,我们用wasm实现了一个简单的四则运算,这里面更多是为了演示复杂文件路径的模块引入,所以直接固定类型为i32而没有用泛型来写,后续继续学习之后,会写一些复杂点的demo来参考学习.