Rust初试---入门、语法、生命周期、与React的结合

491 阅读5分钟

Rust入门教程

Rust是什么

  1. RUST是一种快速、可靠且内存高效的编程语言。
  2. Facebook、Apple、Amazon、Microsoft和Google使用RUST于系统基础架构、加密、虚拟化和更多低级编程。 
  3. Rust 帮助开发人员编写内存高效的快速软件。它是 C++ 或 C 等语言的现代替代品,专注于代码安全和简洁的语法。
  4. Rust 与 JavaScript 完全不同。JavaScript 尝试查找未使用的变量或对象并自动从内存中清除它们。这称为垃圾收集。该语言使开发人员无需考虑手动内存管理。
  5. 使用 Rust,开发人员可以更好地控制内存分配,而不会像 C++ 那样痛苦。

Rust入门–安装

试用传送门:play.rust-lang.org/

Rustup 

Rustup是安装 Rust 的主要工具,既是一个 Rust 安装器也是一个版本管理工具。
macOS、Linux 或其它类 Unix 系统安装:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

卸载命令:

rustup self uninstall

window安装参考:forge.rust-lang.org/infra/other…

  • 在 Windows 上,下载并运行rustup-init.exe.

Cargo 

在安装 Rustup 时,我们也会安装 Rust 构建工具和包管理器的最新稳定版,也就是 Cargo。

  1. cargo build:用来构建项目
  2. cargo run:可以运行项目
  3. cargo test:用来测试项目
  4. cargo doc:可以为项目构建文档
  5. cargo publish:用来将库发布到 crates.io 检查我们是否安装了 Rust 和 Cargo,命令如下:
cargo --version

Rust 支持多种编辑器:

image.png RustDemo

cargo new hello-rust

生成目录如下:

hello-rust       
|- Cargo.toml    // Rust 的清单文件。其中包含了项目的元数据和依赖库
|- src
  |- main.rs     // 编写应用代码文件

main.rs

use ferris_says::say; // from the previous step
use std::io::{stdout, BufWriter};

fn main() {
    let stdout = stdout();
    let message = String::from("Hello 广东靓仔");
    let width = message.chars().count();

    let mut writer = BufWriter::new(stdout.lock());
    say(message.as_bytes(), width, &mut writer).unwrap();
}

运行

cargo run 

可以看到如下结果:

image.png    

Rust生命周期

Rust生命周期出现的目的就是为了消灭悬垂引用

什么是悬垂引用?

下面dangling函数返回字符串对象引用,但在Rust下函数结束时将字符串内存释放,本该return的&s变成孤魂野鬼

image.png

在Rust里面,一个变量只能有一个所有者,这样做可以防止内存多次释放,有助编译器优化

例子

let mut s = String::from("hello");

上面的s拥有"hello"的所有权,但是在一定范围内,这个范围就是叫作用域,一旦离开当前作用域,所有权就要转移,例如离开了当前这个函数的结尾花括号'}'

let mut s = String::from("hello");
let mut j = s;
println!("{}", s);

报错显示s在move后被禁止被借用 image.png

  let mut s = 12;
  let mut j = s;
  println!("{}", s);

试了下,s赋值数字类型的话是没毛病的,s已经move了也可以成功打印出来,这是因为数字类型是默认实现了copy trait,什么是trait?(留意以后文章)。

第一个经典例子

image.png

红色圈范围a,蓝色圈范围b,看得出a是比b小,可以说它更短命,看得出num在a范围里是转移给了res了,但离开a范围本体num理应被销毁,编译器这时候会给你报个错

改动一下

image.png

&num改成num,copy代替move,编译没毛病,对于数字类型来说,这个操作安全

第二个经典例子

编写一个函数,传入2个字符串,返回长度大的那个 image.png

发现报错,原因是编译器推断不出返回值
Rust是宁可先报错,也不放过一个可能的错,就像是逻辑错误,编辑器是推动不出来的,有时是需要人为标注一下

生命周期标注

  1. 加上语法'a,泛型声明,当然叫'objk都可以,这只是约定俗称的简单写法
  2. 我们看到这是人为给这个函数标注返回值跟传入两个参数的生命周期一致,编译器通过

image.png

静态生命周期

`static 表示从生命周期从运行到结束,像js的全局变量
一般来说’static都是省略不写

Rust语法

文档地址:doc.rust-lang.org/std/index.h…

变量

与 js 一样,rust是使用let定义变量的,不过不能直接通过字符串赋值修改

正确使用变量

let mut a = 1; a = 2;
  1. 可以看出 mut 是关键字,mutable的简写。
  2. 温馨提示,变量是可以重复定义的
  3. 用同一个名字重新代表另一个变量实体,其类型、可变属性和值都可以变化。但可变变量赋值仅能发生值的变化。

image.png

基本类型

  1. 整数型(Integer):1
  2. 浮点数型(Floating-Point):2.0
  3. 布尔型:  true 或 false
  4. 字符型(char):  Unicode标量值
  5. 复合类型:
let tup: (i32, f64, u8) = (6017.32);
// tup.0 等于 601
// tup.1 等于 7.3 
// tup.2 等于 2 
let (x, y, z) = tup; 
// y 等于 7.3

函数

fn <函数名> ( <参数> ) <函数体>

image.png

注释

// 这是第一种注释方式  
/* 这是第二种注释方式 */ 
/*  
* 多行注释  
* 多行注释  
* 多行注释  
*/  
/// 三个斜杆的注释

有点不一样

fn main() {
  let mut a = 123;
  a = 4;
  if a < 5 {
    println!("我小于5了");
  }
}

会输出“我小于5了”,可以看出if后面不需要括号

for 、loop 、while

/* for */
let a = [10, 20, 30, 40, 50];
// a.iter() 代表 a 的迭代器(iterator)
for i in a.iter() {
    println!("值为 : {}", i);
}
 
/* loop  */
let s = ['G', 'D', 'L', 'Z'];
let mut i = 0;
let location = loop {
    let ch = s[i];
    if ch == 'D' {
        break i;
    }
    i += 1;
};
 
/* while */
let mut i = 0;
while i < 3 {
    // 循环体
    println!(i);
}

不同的定义,Rust释放资源、清理变量的堆内存不同

image.png

&引用,即变量的间接使用 image.png

数据切片&变量[下标..下标]

image.png

Rust结合React编写组件

原文链接:www.joshfinnie.com/blog/using-…

完整项目代码:github.com/joshfinnie/…

React的demo

自定义我们的 React 构建。使用Webpack从头开始做任何事情都更容易。
让我们package.json通过运行以下命令来初始化我们的:

$ npm init -y

安装 React、Babel、Webpack 和一些不错的软件包。

npm i react react-dom
npm i -D webpack webpack-cli webpack-dev-server html-webpack-plugin 
npm i -D babel-core babel-loader @babel/preset-env @babel/preset-react

创建的文件夹src,public,build,和dist。
打开文件夹中调用index.jsx的src文件并添加以下代码:

import React from "react";
import ReactDOM from "react-dom";

ReactDOM.render(<h1>Hello, world!</h1>, document.getElementById("root"));

设置 babel 和 webpack 配置

.babelrc

{
  "presets": ["@babel/preset-env", "@babel/preset-react"]
}

webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require("path");

module.exports = {
  entry: "./src/index.jsx",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "bundle.[hash].js"
  },
  devServer: {
    compress: true,
    port: 8080,
    hot: true,
    static: './dist',
    historyApiFallback: true,
    open: true
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: __dirname + "/public/index.html",
      filename: "index.html"
    }),
  ],
  mode: "development",
  devtool: 'inline-source-map',
};

public/index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Rusty React</title>
</head>

<body>
  <div id="root"></div>
</body>

</html>

package.json

{
  "name": "rusty-react",
  "version": "1.0.0",
  "description": "A skeleton app showing how to use Rust to leverage Wasm in your React app.",
  "main": "src/index.jsx",
  "scripts": {
    "dev": "webpack server"
  },
  "keywords": [],
  "author": "Josh Finnie <josh@jfin.us",
  "license": "MIT",
  "dependencies": {
    "react": "^17.0.2",
    "react-dom": "^17.0.2"
  },
  "devDependencies": {
    "@babel/preset-env": "^7.16.4",
    "@babel/preset-react": "^7.16.0",
    "babel-core": "^6.26.3",
    "babel-loader": "^8.2.3",
    "html-webpack-plugin": "^5.5.0",
    "webpack": "^5.64.2",
    "webpack-cli": "^4.9.1",
    "webpack-dev-server": "^4.5.0"
  }
}

npm run dev,可以看到如下页面

image.png

Rust 组件

运行

cargo init --lib .

这将创建一个Cargo.toml和一个src/lib.rc文件
为了让 Rust 应用程序准备好将它们的代码转换为 Wasm,我们需要一个名为wasm-bindgen. 
我们还需要告诉编译器这个包是一个cdylib. 为此,我们需要修改我们的Cargo.toml文件:

[package]
name = "rusty-react"
version = "1.0.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2"

测试一下:

cargo build

看到如下图所示:

image.png

为 Rust 添加新目标,我们可以运行以下命令:

rustup target add wasm32-unknown-unknown

这将为我们编译的 Rust 代码提供合适的目标,允许我们将其添加到我们的 React 应用程序中。
让我们更新我们的src/lib.rs文件,使其更有帮助。将现有代码更新为以下内容:

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
extern "C" {
    fn alert(s: &str);
}

#[wasm_bindgen]
pub fn big_computation() {
    alert("Big computation in Rust");
}

#[wasm_bindgen]
pub fn welcome(name: &str) {
   alert(&format!("Hello {}, from Rust!", name));
}

这将为我们提供两个可以在 React 应用程序中使用的函数:

  1. “big_computation”函数
  2. “welcome”函数:需要名称变量

确保我们的 Rust 应用程序正常工作

cargo build --target wasm32-unknown-unknown

安装wasm-bindgen-cli命令行应用程序,以便我们可以利用我们创建的 WebAssembly 代码:

cargo install -f wasm-bindgen-cli

使用 Rust 生成的 WebAssembly 代码并为我们的 React 代码创建一个包装

wasm-bindgen target/wasm32-unknown-unknown/debug/rusty_react.wasm --out-dir build

这会将 Javascript 包装和优化的 Wasm 代码转储到我们的build目录中,以供 React 使用。这就是我们接下来要做的!

React 和 Wasm

在我们的 React 应用程序中使用上述 Wasm 代码 package.json添加如下命令:

 "build:wasm": "cargo build --target wasm32-unknown-unknown",
  "build:bindgen": "wasm-bindgen target/wasm32-unknown-unknown/debug/rusty_react.wasm --out-dir build",
  "build": "npm run build:wasm && npm run build:bindgen && npx webpack",

npm run build

安装另一个 NPM 包,以帮助我们使用 Wasm 进行开发。让我们将其添加到我们的开发依赖项中:

npm i -D @wasm-tool/wasm-pack-plugin

更新我们的webpack.config.js文件以利用新包

const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");

  ...

  plugins: [
    ...
    new WasmPackPlugin({
      crateDirectory: path.resolve(__dirname, ".")
    }),
  ],
  ...
  experiments: {
    asyncWebAssembly: true
  }

运行

pm run build:wasm,npm run build:bindgen

并且npm run build没有错误。
截图如下

image.png

将 WebAssembly 代码添加到我们的 React 组件中

src/index.jsx

import React, { useState } from "react";
import ReactDOM from "react-dom";

const wasm = import("../build/rusty_react");

wasm.then(m => {
  const App = () => {
    const [name, setName] = useState("");
    const handleChange = (e) => {
      setName(e.target.value);
    }
    const handleClick = () => {
      m.welcome(name);
    }

    return (
      <>
        <div>
          <h1>Hi there</h1>
          <button onClick={m.big_computation}>Run Computation</button>
        </div>
        <div>
          <input type="text" onChange={handleChange} />
          <button onClick={handleClick}>Say hello!</button>
        </div>
      </>
    );
  };

  ReactDOM.render(<App />, document.getElementById("root"));
});

npm run dev

image.png

image.png

完整项目代码: github.com/joshfinnie/…