最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 新年新人新气象 | Rust 学习笔记 【Rust 中文精选 2 月刊】

    正文概述 掘金(Rust_Magazine)   2021-03-09   863

    作者:李大狗(李骜华)/ 后期编辑: 张汉东


    新年新目标

    打算在 2021 年学习一门新的编程语言,Rust 是一个很好的标的,一方面它及其具备实用性;另一个方面它也能让我们在更高的层面上理解计算机。

    本系列将是我从Rust小学生开始的Rust学习过程全记录。

    话不多说,我们开整。

    由于是一门新的语言(相对 Java),所以传统的到网上去找一本好的入门教材的方法失效了。

    那我们就来康康 Rust 能做什么有趣的事情,有什么有趣的Repo。

    Substrate(Polkadot公链)、Libra(Facebook链)、WeDPR(FISCO BCOS 隐私保护组件)都是用 Rust 写的,不过评估一下,这些 Repo 的难度太高了,不适合用来作为语言入门。

    后来发现 Rust 在 WebAssembly 方面目前进展很不错:

    新年新人新气象 | Rust 学习笔记 【Rust 中文精选 2 月刊】

    所以,Rust 的学习路线就这么定下来了,从wasm开始!

    检索实例

    既然确定了目标,那么可以开始检索相应的实例。这个实例有两个条件:

    • 光有文章是不行的,必须配套相应的的源码
    • 这个源码必须足够简洁,适合用来入门

    经过一番检索,最后找到了这个:

    git clone 下来,运行了试试,的确可以。

    但感觉不是我想要的,因为前端代码的内容太多了。

    然后打开官方教程:

    看到:

    Yep,感觉我需要的是yew

    Yew 的探索之旅

    首先找到 yew的官网:

    找到它官方的例子:

    结果,运行报错……

    cargo-web is not compatible with web-sys.
    

    遇到问题,第一时间,当然是到官方Repo里去检索啦,然后就搜到这么一条 Issue:

    新年新人新气象 | Rust 学习笔记 【Rust 中文精选 2 月刊】

    建议使用 trunk,妥~

    Trunk 的探索之旅

    跳转到 Trunk Repo:

    发现里面有examples,于是直接 clone 下来运行:

    新年新人新气象 | Rust 学习笔记 【Rust 中文精选 2 月刊】

    执行没问题,很好!

    但是只有一个简单的实例,没法基于这个进行学习,怎么办?

    我们回到 yew 的 Repo 里面,看下有没啥实例。

    Examples 很多,也都能跑通,赞:

    新年新人新气象 | Rust 学习笔记 【Rust 中文精选 2 月刊】

    魔改出 Base64 Encoder!

    在入门一个新的计算机技术的时候,千万不要一开始就从0到1!因为从0到1的难度对新手来说太高。最开始应该先去魔改一个已有的项目。

    我选择的是todomvc,原始是长这样:

    新年新人新气象 | Rust 学习笔记 【Rust 中文精选 2 月刊】

    目的是把它修改成一个 Base64-Encoder:

    新年新人新气象 | Rust 学习笔记 【Rust 中文精选 2 月刊】

    Ok,那我们来看看原始代码:

    ......
        fn view(&self) -> Html {
            let hidden_class = if self.state.entries.is_empty() {
                "hidden"
            } else {
                ""
            };
            html! {
                <div class="todomvc-wrapper">
                    <section class="todoapp">
                        <header class="header">
                            <h1>{ "todos" }</h1>
                            { self.view_input() }
                        </header>
                        <section class=classes!("main", hidden_class)>
                            <input
                                type="checkbox"
                                class="toggle-all"
                                id="toggle-all"
                                checked=self.state.is_all_completed()
                                onclick=self.link.callback(|_| Msg::ToggleAll)
                            />
                            <label for="toggle-all" />
                            <ul class="todo-list">
                                { for self.state.entries.iter().filter(|e| self.state.filter.fits(e)).enumerate().map(|e| self.view_entry(e)) }
                            </ul>
                        </section>
                        <footer class=classes!("footer", hidden_class)>
                            <span class="todo-count">
                                <strong>{ self.state.total() }</strong>
                                { " item(s) left" }
                            </span>
                            <ul class="filters">
                                { for Filter::iter().map(|flt| self.view_filter(flt)) }
                            </ul>
                            <button class="clear-completed" onclick=self.link.callback(|_| Msg::ClearCompleted)>
                                { format!("Clear completed ({})", self.state.total_completed()) }
                            </button>
                        </footer>
                    </section>
                    <footer class="info">
                        <p>{ "Double-click to edit a todo" }</p>
                        <p>{ "Written by " }<a href="https://github.com/DenisKolodin/" target="_blank">{ "Denis Kolodin" }</a></p>
                        <p>{ "Part of " }<a href="http://todomvc.com/" target="_blank">{ "TodoMVC" }</a></p>
                    </footer>
                </div>
            }
        }
    }
    ......
    

    挺好,这个就是前端部分了,我们把它删减一下:

        fn view(&self) -> Html {
            let hidden_class = if self.state.entries.is_empty() {
                "hidden"
            } else {
                ""
            };
            html! {
                <div class="todomvc-wrapper">
                    <h1>{ "encode/decode" }</h1>
                    { self.view_input() }
                    <section class=classes!("main", hidden_class)>
                        <ul class="todo-list">
                            { for self.state.entries.iter().filter(|e| self.state.filter.fits(e)).enumerate().map(|e| self.view_entry(e)) }
                        </ul>
                    </section>
                </div>
            }
        }
    

    我们可以看到,输入的逻辑在view_input()这个地方,于是我们找到那个函数:

    fn view_input(&self) -> Html {
            html! {
                // You can use standard Rust comments. One line:
                // <li></li>
                <input
                    class="new-todo"
              			// 改掉replaceholder
                    placeholder="What needs to be encode/decode?"
                    value=&self.state.value
                    oninput=self.link.callback(|e: InputData| Msg::Update(e.value))
                    onkeypress=self.link.batch_callback(|e: KeyboardEvent| {
                        if e.key() == "Enter" { Some(Msg::Add) } else { None }
                    })
                />
                /* Or multiline:
                <ul>
                    <li></li>
                </ul>
                */
            }
        }
    

    再找到Msg::Add

    fn update(&mut self, msg: Self::Message) -> ShouldRender {
            match msg {
                Msg::Add => {
                    //info!("add things");
                    let description = self.state.value.trim();
                    let description_handled = format!("{}: {}", description, encode(description.to_string()));
    
                    if !description.is_empty() {
                        let entry = Entry {
                            description: description_handled,
                            completed: false,
                            editing: false,
                        };
                        //info!("{}", entry.description);
                        self.state.entries.push(entry);
                    }
                    self.state.value = "".to_string();
                }
    ......
    

    这个时候,我想先调试一下,因此需要把一些数据打印出来。

    这个时候,首先想到的是print大法:

    println!("Input: {}", val);
    

    但是,在trunk serve命令中,println!这个函数失效了!

    trunkyew的 Repo 中进行检索,均未找到解决方案。

    但是随即发现yew有 Discord Chatroom,于是乎进去搜索聊天记录。

    新年新人新气象 | Rust 学习笔记 【Rust 中文精选 2 月刊】

    Yummy,这里提到只要使用wasm-logger即可。

    在项目里添加wasm-logger

    ......
    // in the first of main.rs
    #[macro_use] extern crate log;
    ......
    fn main() {
    		// init wasm logger!
        wasm_logger::init(wasm_logger::Config::default());
        yew::start_app::<Model>();
    }
    

    调用试试看:

    fn update(&mut self, msg: Self::Message) -> ShouldRender {
            match msg {
                Msg::Add => {
                    info!("add things");
    ......
    

    妥了!

    新年新人新气象 | Rust 学习笔记 【Rust 中文精选 2 月刊】

    接下来找到Rust Base64 的库,调用之(修改的地方用new标出了):

    ......
    use base64::{encode, decode};
    ......
    fn update(&mut self, msg: Self::Message) -> ShouldRender {
            match msg {
                Msg::Add => {
                    // new
                    info!("add things");
                    let description = self.state.value.trim();
                    // new
                    let description_handled = format!("{}: {}", description, encode(description.to_string()));
    
                    if !description.is_empty() {
                        let entry = Entry {
                          	// new
                            description: description_handled,
                            completed: false,
                            editing: false,
                        };
                      	// new
                        info!("{}", entry.description);
                        self.state.entries.push(entry);
                    }
                    self.state.value = "".to_string();
                }
    

    运行之。

    Okay,Base64-Encoder就做好了!

    效果:

    新年新人新气象 | Rust 学习笔记 【Rust 中文精选 2 月刊】

    Cargo.toml最后长这样:

    [package]
    name = "encoder"
    version = "0.1.0"
    authors = ["Denis Kolodin <deniskolodin@gmail.com>"]
    edition = "2018"
    
    [dependencies]
    strum = "0.20"
    strum_macros = "0.20"
    serde = "1"
    serde_derive = "1"
    yew = { path = "./packages/yew" }
    yew-services = { path = "./packages/yew-services" }
    
    log = "0.4.6"
    wasm-logger = "0.2.0"
    base64 = "0.13.0"
    

    生成 ETH 公私钥与地址

    本篇是 Rust 学习笔记的第二篇。在第一篇里,我们魔改出了一个 Encoder,现在我们继续延续我们的魔改之路,挑战一个难度+1的Repo:

    魔改目标 0x1:

    但在魔改之前,笔者首先要对上一篇文章稍作补充,总结一下上篇文章中所涉及的知识点。

    上篇文章中所涉及的知识点

    • 变量的赋值
    • format!函数(连接字符串)
    • 库的添加与使用,以wasm-logger为例
    • trunk 与 yew 结合,让Rust程序 wasm 化,使其在浏览器中可访问

    跑一遍 wagyu

    首先要验证这个库符合我们的需求,所以按照 Repo 中的 Readme,采用源码的方式跑一遍。

    # Download the source code
    git clone https://github.com/AleoHQ/wagyu
    cd wagyu
    
    # Build in release mode
    $ cargo build --release
    ./target/release/wagyu
    

    成功:

    新年新人新气象 | Rust 学习笔记 【Rust 中文精选 2 月刊】

    在这个过程里,我们学习到了 cargo 的更多用法:

    $ cargo run # 直接执行
    $ cargo build # build 出 debug 版本,可执行文件在 ./target/debug 目录下
    $ cargo build --release # build 出 正式版本(release version),可执行文件在 ./target/release 下
    

    研究 wagyu 代码

    首先喵一眼目录结构:

    .
    ├── AUTHORS
    ├── Cargo.lock
    ├── Cargo.toml
    ├── LICENSE-APACHE
    ├── LICENSE-MIT
    ├── README.md
    ├── bitcoin
    ├── ethereum
    ├── model
    ├── monero
    ├── target
    ├── zcash
    └── wagyu
          ├── cli
          │   ├── bitcoin.rs
          │   ├── ethereum.rs
          │   ├── mod.rs
          │   ├── monero.rs
          │   ├── parameters
          │   └── zcash.rs
          ├── lib.rs
          └── main.rs
    
    

    我们可以看到,主入口是wagyu

    wagyumain.rs中,会对cli目录下的子模块进行调用,进而对和cli平级的子模块进行调用。

    其代码如下:

    fn main() -> Result<(), CLIError> {
        let arguments = App::new("wagyu")
            .version("v0.6.3")
            .about("Generate a wallet for Bitcoin, Ethereum, Monero, and Zcash")
            .author("Aleo <hello@aleo.org>")
            .settings(&[
                AppSettings::ColoredHelp,
                AppSettings::DisableHelpSubcommand,
                AppSettings::DisableVersion,
                AppSettings::SubcommandRequiredElseHelp,
            ])
            .subcommands(vec![
                BitcoinCLI::new(),
                EthereumCLI::new(),
                MoneroCLI::new(),
                ZcashCLI::new(),
            ])
            .set_term_width(0)
            .get_matches();
    
        match arguments.subcommand() {
            ("bitcoin", Some(arguments)) => BitcoinCLI::print(BitcoinCLI::parse(arguments)?),
            ("ethereum", Some(arguments)) => EthereumCLI::print(EthereumCLI::parse(arguments)?),
            ("monero", Some(arguments)) => MoneroCLI::print(MoneroCLI::parse(arguments)?),
            ("zcash", Some(arguments)) => ZcashCLI::print(ZcashCLI::parse(arguments)?),
            _ => unreachable!(),
        }
    }
    

    我们再进入wagyu > cli > ethereum.rs目录下,发现里面有个简单的函数:

        pub fn new<R: Rng>(rng: &mut R) -> Result<Self, CLIError> {
            let private_key = EthereumPrivateKey::new(rng)?;
            let public_key = private_key.to_public_key();
            let address = public_key.to_address(&EthereumFormat::Standard)?;
            Ok(Self {
                private_key: Some(private_key.to_string()),
                public_key: Some(public_key.to_string()),
                address: Some(address.to_string()),
                ..Default::default()
            })
        }
    

    很好,就拿这个改造了!

    复制必要文件到新项目

    1. 新建项目
    $ cargo new hello-crypto-rust
    

    或者直接把上一个项目复制一份。

    1. wagyuCargo.toml中的必要内容复制过来
    [dependencies]
    log = "0.4"
    pretty_env_logger = "0.3"
    
    wagyu-ethereum = { path = "./ethereum", version = "0.6.3" }
    wagyu-model = { path = "./model", version = "0.6.3" }
    
    arrayvec = { version = "0.5.1" }
    base58 = { version = "0.1" }
    clap = { version = "~2.33.1" }
    colored = { version = "1.9" }
    digest = { version = "0.9.0" }
    either = { version = "1.5.3" }
    failure = { version = "0.1.8" }
    hex = { version = "0.4.2" }
    lazy_static = { version = "1.4.0" }
    rand = { version = "0.7" }
    rand_core = { version = "0.5.1" }
    safemem = { version = "0.3.3" }
    serde = { version = "1.0", features = ["derive"] }
    serde_json = { version = "1.0" }
    tiny-keccak = { version = "1.4" }
    
    [profile.release]
    opt-level = 3
    lto = "thin"
    incremental = true
    
    [profile.bench]
    opt-level = 3
    debug = false
    rpath = false
    lto = "thin"
    incremental = true
    debug-assertions = false
    
    [profile.dev]
    opt-level = 0
    
    [profile.test]
    opt-level = 3
    incremental = true
    debug-assertions = true
    debug = true
    
    1. ethereummodel两个文件夹复制到hello-crypto-rust目录下

    此时的文件目录是这个样子的:

    .
    ├── Cargo.lock
    ├── Cargo.toml
    ├── ethereum
    ├── model
    ├── src
    └── target
    

    补充代码

    1. 补充lib.rs文件

    src目录下新建lib.rs文件,内容:

    pub extern crate wagyu_ethereum as ethereum;
    pub extern crate wagyu_model as model;
    extern crate pretty_env_logger;
    

    作用是加载外部 crate,更详细的说明可见:

    1. 编写main.rs文件。

    首先引用必要的外部模块:

    use rand::{rngs::StdRng};
    use rand_core::SeedableRng;
    use hello_crypto_rust::ethereum::{EthereumPrivateKey, EthereumFormat};
    use hello_crypto_rust::model::{PrivateKey, PrivateKeyError, AddressError, PublicKeyError, PublicKey};
    
    #[macro_use] extern crate log;
    

    然后我们编写主函数:

    fn main(){
        pretty_env_logger::init();  // 初始化 pretty_env_logger 模块
        new(); //调用new函数
    }
    

    new()函数:

    pub fn new() -> Result<EthereumPrivateKey, CreateError> {
        let rng = &mut StdRng::from_entropy();
        let private_key = EthereumPrivateKey::new(rng)?;
        info!("priv: {}", private_key.to_string());
        let public_key = private_key.to_public_key();
        info!("pub: {}", public_key.to_string());
        let address = public_key.to_address(&EthereumFormat::Standard)?;
        info!("addr: {}", address.to_string());
        Ok(private_key)
    }
    

    我们这里使用了相对于println!更高级的输出方式,通过log输出。

    这里有个关键的语法糖——?,用于错误处理。

    两个等价的函数,一个使用了?,一个没有:

    fn not_use_question_mark() {
        let a = 10;                                                   // 把这里改成 9 就会报错.
        let half = halves_if_even(a);
        let half = match half {
            Ok(item) => item,
            Err(e) => panic!(e),
        };
        assert_eq!(half, 5);
    }
    
    
    fn use_question_mark<'a >() -> Result<i32, &'a str> {              // 这里必须要返回Result
        let a = 10;
        let half = halves_if_even(a)?;                     // 因为?要求其所在的函数必须要返回Result
        assert_eq!(half, 5);
        Ok(half)                                                                   
    }
    

    然后,我们定义一下枚举类型CreateError,里面会囊括AddressErrorPrivateKeyErrorPublicKeyError

    pub enum CreateError {
        AddressError(AddressError),
        PrivateKeyError(PrivateKeyError),
        PublicKeyError(PublicKeyError)
    }
    
    impl From<AddressError> for CreateError {
        fn from(error: AddressError) -> Self {
            CreateError::AddressError(error)
        }
    }
    
    impl From<PrivateKeyError> for CreateError {
        fn from(error: PrivateKeyError) -> Self {
            CreateError::PrivateKeyError(error)
        }
    }
    
    impl From<PublicKeyError> for CreateError {
        fn from(error: PublicKeyError) -> Self {
            CreateError::PublicKeyError(error)
        }
    }
    

    Try It!

    实现成功:

    新年新人新气象 | Rust 学习笔记 【Rust 中文精选 2 月刊】

    本篇所涉及的知识点

    • cargo 的更多用法
    • lib.rs的用法
    • 函数与函数返回值
    • pretty_env_logger的用法
    • 枚举类型,以CreateError为例

    作者简介:

    李大狗(李骜华),上海对外经贸大学区块链技术与应用研究中心副主任、柏链教育 CTO、FISCO BCOS(微众银行区块链框架)区块链认证讲师、5 年区块链工程师、北京大学硕士。 研究领域包括:区块链系统、共识机制、智能合约、区块链应用、数字身份等。


    起源地下载网 » 新年新人新气象 | Rust 学习笔记 【Rust 中文精选 2 月刊】

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元