Giter Site home page Giter Site logo

notebook's Introduction

Brief notes

💡 Sharing some engineering experiences that are not quite summarized elsewhere. They are put at issues.

notebook's People

Contributors

thorseraq avatar

Watchers

 avatar

notebook's Issues

UPnP(Universal Plug and Play) 内网穿透方案

大部分家庭宽带没有固定公网 IP,家用光猫被运营商的设备通过 DHCP 分配一个随机有时限(过期再重新分配)的公网IP,家里的设备再通过光猫 DHCP 分配私有 IP,光猫使用 NAT/NATP 协议将私有 IP 和公网 IP 映射来复用这个公网 IP,因此互联网上的两台电脑无法直接通过公网 IP 通信(因为公网 IP 指向的实际是光猫)。UPnP 是绕过 NAT/NATP 的一种协议,使用超级管理员账号登录家用光猫,大部分光猫支持开启 UPnP。uPnP 可以选择开放指定端口,配合相应使用 UPnP 的客户端就可以实现内网穿透。

例如 parsec 是一个远程控制软件,基于 UPnP 实现内网穿透,可以 P2P 远程控制,不需要经过服务器。家里的上行带宽如果有个几十 MB(和直播推流非常像),拿来远程玩游戏延迟非常低。。

UPnP 可以暴露一个或多个端口到公网,如果使用该端口的客户端/服务器有漏洞(例如接收任何网络请求,解析请求里携带的路径,返回主机对应路径下的文件), 或者电脑有恶意软件获取信息并且通过这个端口把信息上传出去,就会有问题,所以 UPnP 的文章大多都会提一嘴风险

What Is An Open Port? Risks, Port Scanning & Detection (thecyphere.com)

Simple demo reproducing that GDB debugging does not pause child thread sleep

Code

use std::thread;
use std::thread::sleep;

fn main() {
    thread::spawn(|| {
        sleep(std::time::Duration::from_secs(2));
        println!("from other thread");
    });
    println!("from main thread");
    sleep(std::time::Duration::from_millis(10)); // 在这里打断点,多停会儿,再继续跑,两条 println! 都会打印
}

Reason

Rust uses gdb or LLDB to stop program in user mode. But sleep runs in kernal mode, once sleep is called, gdb cannot stop the clock. So when the program is resumed, the clock may have already finished.

CRDT conflict resolving semantic(待施工)

import * as Y from 'yjs'

// Happen-before relationship cannot be established because the updates of two replicas are operated "offline" and then
// synchronized. When cases of conflicts happen, there are some circumstances where there's no recognized semantic
// resolving strategy. In this case, yjs thinks doc with larger clientId has more weight when the outcome is not defined
// by CRDT operation.
function withoutHappenBeforeRelationship() {
    const doc1 = new Y.Doc()
    // For experiment, override the default random clientId
    doc1.clientID = 1;
    const doc2 = new Y.Doc()
    doc2.clientID = 2; // try decrease clientId to 0, see the outcome

    doc1.getArray('my array').insert(0, [1, 2, 3]);
    doc2.getArray('my array').insert(0, [4, 5, 6]);

    let map = doc1.getMap('my map');
    map.set('a', 1);
    const ymapNested = new Y.Map();
    ymapNested.set('key', 'value');
    map.set('ymapNested', ymapNested);

    doc2.getMap("my map").set('a', 2);

    // Simulate an offline circumstance, where doc1 and doc2 have separately updated its doc content.
    // In this case, we cannot establish a happen-before relationship between doc1's update and doc2's update.
    const state1 = Y.encodeStateAsUpdate(doc1);
    const state2 = Y.encodeStateAsUpdate(doc2);
    Y.applyUpdate(doc1, state2);
    Y.applyUpdate(doc2, state1);

    console.log(doc1.getArray('my array').toJSON());
    console.log(doc1.getMap('my map').toJSON());
    console.log(doc2.getArray('my array').toJSON());
    console.log(doc2.getMap('my map').toJSON());

    // output: if doc2.clientID = 2;
    // [ 1, 2, 3, 4, 5, 6 ]
    // { a: 2, ymapNested: { key: 'value' } }
    // [ 1, 2, 3, 4, 5, 6 ]
    // { a: 2, ymapNested: { key: 'value' } }

    // output: if doc2.clientID = 0;
    // [ 4, 5, 6, 1, 2, 3 ]
    // { a: 1, ymapNested: { key: 'value' } }
    // [ 4, 5, 6, 1, 2, 3 ]
    // { a: 1, ymapNested: { key: 'value' } }
}

// clientId doesn't affect the result. Because happend-before relationship is established by the order of updates.
function withHappenBeforeRelationship() {
    const doc1 = new Y.Doc()
    // For experiment, override the default random clientId
    doc1.clientID = 1;
    const doc2 = new Y.Doc()
    doc2.clientID = 0; // no matter what clientId is, the result is the same

    doc1.on('update', update => {
        Y.applyUpdate(doc2, update)
    })

    doc2.on('update', update => {
        Y.applyUpdate(doc1, update)
    })

    doc1.getArray('my array').insert(0, [1, 2, 3]);
    doc2.getArray('my array').insert(0, [4, 5, 6]);

    let map = doc1.getMap('my map');
    map.set('a', 1);
    const ymapNested = new Y.Map();
    ymapNested.set('key', 'value');
    map.set('ymapNested', ymapNested);

    doc2.getMap("my map").set('a', 2);

    console.log(doc1.getArray('my array').toJSON());
    console.log(doc1.getMap('my map').toJSON());
    console.log(doc2.getArray('my array').toJSON());
    console.log(doc2.getMap('my map').toJSON());

    // [ 4, 5, 6, 1, 2, 3 ]
    // { a: 2, ymapNested: { key: 'value' } }
    // [ 4, 5, 6, 1, 2, 3 ]
    // { a: 2, ymapNested: { key: 'value' } }
}

function withHappenBeforeRelationshipWithConflictType() {
    const doc1 = new Y.Doc()
    // For experiment, override the default random clientId
    doc1.clientID = 1;
    const doc2 = new Y.Doc()
    doc2.clientID = 2;

    doc1.on('update', update => {
        Y.applyUpdate(doc2, update)
    })

    doc2.on('update', update => {
        Y.applyUpdate(doc1, update)
    })

    doc1.getArray('key').insert(0, [1, 2, 3]);
    doc2.getMap('key').set('0', 1);

    console.log(doc1.toJSON());
    console.log(doc2.toJSON());

    // { key: [ 1, 2, 3 ] }
    // { key: { '0': 1 } }
}

// withHappenBeforeRelationship();
// withoutHappenBeforeRelationship();
withHappenBeforeRelationshipWithConflictType();

Triggering of browser copy events and clipboard data handling

  • There are four ways to trigger a copy event

    1. User triggered (ctrl+c/right click copy). tip: Can only be triggered by the user

    2. triggered by document.execCommand('copy'). tip: Compatibility

    3. New API navigator.clipboard. tip: Compatibility

    // writeText(data: string): Promise<void>; generic is void, no return value on resolve
    navigator.clipboard.writeText('Text to be copied')
      .then(() => {
        console.log('Text copied to clipboard');
      })
      .catch(err => {
        // This can happen if the user denies clipboard permissions:
        console.error('Could not copy text: ', err);
      });
    1. use EventTarget.dispatchEvent(new ClipboardEvent('copy', { bubbles: true })) 。tip:There is no data in the clipboard, it's a "fake" copy, but we can use the listener to listen to the copy event.
  • When using method 1, 2,e.clipboardData: DataTransfer exists in the custom handler handleCopy(e: ClipboardEvent) . When using method 4, e.clipboardData does not exist. e.clipboardData is the clipboard data, which is in memory (so it can be pasted across applications).

  • Think of e.clipboardData as key-value pair,with the key being a MIME type or a custom type, as long as it is a string, so it is easy to copy and paste across applications using the agreed-upon MIME type. Value is a string type, usually JSON serialized data. types holds all the key, getData(), setData() operations key, value. w3c's demo

  • Use new DataTransfer() to construct clipboardData cannot associate it with memory. So the key to copy is to get the clipboardData, to achieve this, use document.execCommand(copy), and construct a temporary DOM element append to the body (so that events can bubble to that element), and listen to the copy event.

  • So the timing of the copy trigger, and the content copied to the clipboard can be customized.

Test gantt

gantt
dateFormat YYYY-MM-DD
TypeScript 4.9 Stabilization Period : 2022-10-28, 2022-11-11
TypeScript 5.0 Beta Development : 2022-10-28, 2023-01-19
TypeScript 5.0 RC Development : 2023-01-19, 2023-02-23
TypeScript 5.0 Stabilization Period : 2023-02-23, 2023-03-14
todayMarker stroke-width:5px,stroke:#0f0,opacity:0.5

内网环境下主机“远程”控制

背景

最近想尝试一下在内网环境下通过路由器转发实现远程控制,画质和延迟应该都更好,毕竟不受运营商带宽限制,不过大部分远程控制软件都需要经过服务器中转。 VNC 是可以做内网连接的,不过缺点是不能上传(forward)主机声音。。。

How to

在路由器组的局域网下,一台主机运行 VNC Server(服务端),另一台主机运行 VNC Viewer(客户端),VNC Server 需要设置一下登录鉴权方式,可以是主机账号,也可以是 VNC password,然后 VNC Viewer 的主机就可以通过另一台主机的私有 IP,用设置的鉴权方式连接。有个坑是 VNC Server 只有企业版才支持本地连接(Direct Connection),可以注册个账号试用企业版。VNC Server 默认使用 5900 端口,运行 VNC Server 的主机记得防火墙打开 5900 端口。另外可以在路由器设置一下物理地址 MAC 和 IP 绑定,固定下电脑的 IP,每次连接就不用找另一台电脑的 IP (连接好之后就可以开心的斗地主了

VNC Server 可以装在 windows, mac, linux, 树莓派甚至 ios, android ?,经过实测的有 mac(运行 VNC Viewer) 控制 windows(运行 VNC Server) 可以,windows(运行 VNC Viewer) 控制 mac(运行 VNC Server) 画面是黑屏,不能控制

image

7BBBA9C7-2A09-4F01-94CD-82E914BEE1F8

tokio 基本概念

  • rust 中的 async 函数是懒惰的,调用一个 async 函数并不会真正执行任务,而是返回 Future 对象,对 Future 对象 .await 后创建新的 task 分配给 worker thread 执行

  • 一个 tokio task 是一个绿色线程, tokio::spawn(async {...}) 创建一个 tokio task,将 task 丢到任务队列中,等 worker thread 来执行,.await 才会执行的意思是,在 .await 时会把当前这条语句生成新 task 加入任务队列。

  • tokio task 中的 .await 会把执行权交回给线程。因此 std::thread::sleep(std::time::Duration::from_secs(10000)); 不会立刻让出执行权。tokio 把大多数 std 库的函数都重新实现了一遍,返回的值都实现了 Future trait,因此可以 await 让出执行权。

    在 Tokio 中,一个 Future 可以选择将执行权交还给调度程序,而不是一直占用着 CPU。当一个 Future 调用 .await 时,它将把执行权交还给使用 Tokio 的线程池中闲置的线程。因此,在执行 .await 期间,Tokio 可以调度其他任务来利用 CPU 时间。

    std::thread::sleep() 函数会一直暂停当前线程,直到指定的时间已过,这将导致线程一直阻塞,无法释放 CPU 给其他线程使用。而在使用 Tokio 的方式中,这样的调用不会让出执行权,因此不能与 Tokio 的异步模型配合使用。

    为了与 Tokio 的异步模型兼容,Tokio 实现了自己的版本的许多标准库函数。在这些 Tokio 的实现中,会使用异步 I/O 和非阻塞 I/O 操作来代替阻塞 I/O 操作,从而让出执行权,允许 Tokio 在等待 I/O 操作时切换到其他任务。这样就可以更好地利用线程的 CPU 时间,提高整个程序的效率。

  • tokio 的线程分为 Worker threads 和 Blocking threads。Worker threads 一旦初始化,数量就不会变化了,但每个 worker thread 并发执行的异步任务无上限,默认的 tokio runtime 的 worker threads 数量是 CPU 核数。Blocking threads 需要手动 spawn_blocking 触发,而且每个 thread 是同步执行任务,所以没有并发,单个 Blocking threads 只能执行一个 task 到完,再执行下一个,好处是没有竞争问题。(针对只有一个 blocking thread 的情况,例如如果是多个,仍然会有抢数据库锁的问题)

  • tokio::select! 的 match arm 有自动补全 .await 的语法糖,并且可选的接受这个异步任务的返回值。

    async fn do_stuff_async() {
        // async work
    }
    
    async fn more_async_work() {
        // more here
    }
    
    #[tokio::main]
    async fn main() {
        tokio::select! {
            _ = do_stuff_async() => {
                println!("do_stuff_async() completed first")
            }
            _ = more_async_work() => {
                println!("more_async_work() completed first")
            }
        };
    }
  • thread::spawntokio::task::spawn_blocking 都可以用来创建新线程来运行任务

    • tokio::task::spawn_blocking 必须在 tokio 的 runtime 里使用。

      #[tokio::main]
      async fn main() {
          tokio::task::spawn_blocking(|| {
              println!("start");
              sleep(std::time::Duration::from_secs(2));
              println!("end");
          });
      
          println!("main ended");
      }
      // main ended
      // start
      // end
    • thread::spawn 是启动一个 OS 线程,可以用来开新的 tokio runtime

      std::thread::spawn(move || {
          let Ok(rt) = tokio::runtime::Runtime::new() else {
              return error!("Failed to create runtime");
          };
          rt.block_on(async move {
              loop {
                  ...
              }
      
              debug!("end sync thread");
          });
      });

Rust 文章和笔记

本文记录读 rust-lang book 时的笔记,主要目的是帮助快速回忆关键概念

Data Types

  • char 总是四个字节。字符串有 &str, String 两种类型,字符串常量和字符串切片的类型是 &str, 堆上的字符串是 String。 字符串是字节流,字节流中可能多个字节组成一个字符: String slice range indices must occur at valid UTF-8 character boundaries. If you attempt to create a string slice in the middle of a multibyte character, your program will exit with an error.
  • 一个特殊的 tuple: (), 有个名字: unit。 The tuple without any values has a special name, unit. This value and its corresponding type are both written () and represent an empty value or an empty return type. Expressions implicitly return the unit value if they don’t return any other value.

Functions

  • Statements vs Expression

Statements are instructions that perform some action and do not return a value. Expressions evaluate to a resulting value. Let’s look at some examples.

  • Expression 类型

    • Calling a function
    • Calling a macro
    • A new scope block created with curly brackets
  • 函数形参必须指定类型

Control Flow

  • If 后不加括号,像python

  • if 后的条件不会自动转换为 boolean

  • 条件表达式

let condition = true;
let number = if condition {5} else {6};
  • 循环

    • loop 比较像 do while, 而且可以在 break 处返回值

    • loop 可以有标签

    • 有 for in 利用迭代器的循环

      let a = [10, 20, 30, 40, 50];
      for element in a {
        println!("the value is: {element}");
      }
    • for in 可以结合 Range

      fn main() {
          for number in (1..4).rev() {
              println!("{number}!");
          }
          println!("LIFTOFF!!!");
      }

Understanding Ownership

  • 简介:Stack 比 Heap 更快,首先在 Stack 上分配的内存大小确定,其次 Stack 的地址更加连续(可以利用时间、空间局部性,减少 CPU 缓存交换)

  • rust 对内存的管理:When a variable comes into scope, it is valid. It remains valid until it goes out of scope,回收内存调用 drop()

  • 为了避免对同一块堆内存的两次回收,使用 move 机制

  • If a type implements the Copy trait, variables that use it do not move, but rather are trivially copied, making them still valid after assignment to another variable. 在 Stack-Only Data: Copy 看哪些类型实现了 Copy

  • 一个概念 creating a reference - borrowing

  • mutable reference 只能有一个,这个机制可以避免 racing condition。Immutable 的 reference 数量没有限制,但是 Immutable 和 mutable 的 reference 结合起来,immuatable 也会限制 mutable。

    let mut s = String::from("hello");
    
    let r1 = &mut s;
    let r2 = &mut s; // wrong. cannot borrow `s` as mutable more than once at a time
    
    println!("{}, {}", r1, r2);

    但是下面这种情况可以,因为不会同时存在两个对 s 的引用

     let mut s = String::from("hello");
    
      {
          let r1 = &mut s;
      } // r1 goes out of scope here, so we can make a new reference with no problems.
    
      let r2 = &mut s;
  • Reference 的作用域更短:Note that a reference’s scope starts from where it is introduced and continues through the last time that reference is used.

  • rust 不会存在 dangling reference: In Rust, by contrast, the compiler guarantees that references will never be dangling references: if you have a reference to some data, the compiler will ensure that the data will not go out of scope before the reference to the data does.

  • Slice 没有 ownership: A slice is a kind of reference, so it does not have ownership. string_slice 的写法:&str. &[1,2,3,4,5][1..3] 的切片类型是 &[i32],它的含义是数组 [1,2,3,4,5] 范围为 [1,3) 的切片

Using Structs to Structure Related Data

Structs 引入

  • 和 TypeScript 有点像,创建对象有简化写法: field init shorthand syntax,还有解构对象: update syntax

  • update syntax 等于 assignment,每个字段分别判断是 move 还是 copy

  • Tuple Struct: struct Color(i32, i32, i32); let black = Color(0, 0, 0);

  • unit-like structs: Unit-like structs can be useful when you need to implement a trait on some type but don’t have any data that you want to store in the type itself.

  • Struct 的字段如果是 reference 类型,需要给相应的字段指定 lifetime

  • dbg! macro 会获得 ownership, 但是也会返回 ownership,所以可以接住。如果调用他的时候传的是引用,就不需要。An Example Program Using Structs - The Rust Programming Language (rust-lang.org)

    We can put dbg! around the expression 30 * scale and, because dbg! returns ownership of the expression’s value, the width field will get the same value as if we didn’t have the dbg! call there. We don’t want dbg! to take ownership of rect1, so we use a reference to rect1 in the next call.

Method 引入

  • &self 是 self: &Self 的简写:Methods must have a parameter named self of type Self for their first parameter, so Rust lets you abbreviate this with only the name self in the first parameter spot.
impl Rectangle {
    pub fn area(&self) -> u32 {
        self.height * self.width
    }
}
  • impl block 中定义的方法也可以为 associated functions:We can define associated functions that don’t have self as their first parameter (and thus are not methods) because they don’t need an instance of the type to work with. We’ve already used one function like this: the String::from function that’s defined on the String type. 有点像静态方法

  • 调用 associated 方法使用::, 和模块引用相同

Enums and Pattern Matching

  • 概念 Enum Variants. Some, None 都是 variants

    enum Option<T> {
        None,
        Some(T),
    }
  • coin 是 expression, Coin::Penny 是 pattern

    fn value_in_cents(coin: Coin) -> u8 {
        match coin {
            Coin::Penny => 1,
            Coin::Nickel => 5,
            Coin::Dime => 10,
            Coin::Quarter => 25,
        }
    }
  • 概念 Pattern arm, An arm has two parts: a pattern and some code.

  • 概念 catch-all arm: If we put the catch-all arm earlier, the other arms would never run, so Rust will warn us if we add arms after a catch-all!

    match dice_roll {
            3 => add_fancy_hat(),
            7 => remove_fancy_hat(),
            other => move_player(other), // catch-all arm
        }
    match dice_roll {
            3 => add_fancy_hat(),
            7 => remove_fancy_hat(),
            _ => reroll(), // catch-all, but don't use the value
        }
  • 什么都不做可以用空函数,也可以用 empty tuple

    let dice_roll = 9;
        match dice_roll {
            3 => add_fancy_hat(),
            7 => remove_fancy_hat(),
            _ => (), // empty tuple
        }

Packages, Crates, and Modules

基础概念

  • Binary crates 和 library crates 数量的约定

    • A package can contain multiple binary crates and optionally one library crate.
    • A package must contain at least one crate, whether that’s a library or binary crate.
    • A package can have multiple binary crates by placing files in the src/bin directory: each file will be a separate binary crate.
  • module system 相关的名词

    • Packages: A Cargo feature that lets you build, test, and share crates
    • Crates: A tree of modules that produces a library or executable
    • Modules and use: Let you control the organization, scope, and privacy of paths
    • Paths: A way of naming an item, such as a struct, function, or module
  • A crate can come in one of two forms: a binary crate or a library crate.

  • 关于 binary crate 的约定:Cargo follows a convention that src/main.rs is the crate root of a binary crate with the same name as the package.

  • 关于 library crate 的约定:If the package directory contains src/lib.rs, the package contains a library crate with the same name as the package, and src/lib.rs is its crate root.

  • 编译时:Cargo passes the crate root files to rustc to build the library or binary.

  • Crate 命名空间从 crate 开始,例如 crate::mod1::sub_mod_of_mod1::Circle,也可以通过 use mod1::sub_mod_of_mod1 来简写为 sub_mod_of_mod1::Circle。一个 module tree 的例子:The entire module tree is rooted under the implicit module named crate.

    crate
     └── front_of_house
         ├── hosting
         │   ├── add_to_waitlist
         │   └── seat_at_table
         └── serving
             ├── take_order
             ├── serve_order
             └── take_payment

模块路径解析 Paths for Referring to an Item in the Module Tree

  • 完整的路径解析规则:Defining Modules to Control Scope and Privacy - The Rust Programming Language (rust-lang.org).

  • 路径解析种类:A path can take two forms:

    • An absolute path is the full path starting from a crate root; for code from an external crate, the absolute path begins with the crate name, and for code from the current crate, it starts with the literal crate.

    • A relative path starts from the current module and uses self, super, or an identifier in the current module (Starting with a module name means that the path is relative). Example: front_of_house/hosting/add_to_waitlist

  • 可见性:

    • 子孙模块的内容对祖先模块默认是 private 的:In Rust, all items (functions, methods, structs, enums, modules, and constants) are private to parent modules by default.

    • 子孙模块可以使用祖先模块的内容:Items in a parent module can’t use the private items inside child modules, but items in child modules can use the items in their ancestor modules.

    • 注意 inline modules 是当前模块其他内容的 silbing,下面 front_of_houseeat_at_restaurant 的 silbing,所以不需要 pub 就可以访问。

      mod front_of_house {
          pub mod hosting {
              pub fn add_to_waitlist() {}
          }
      }
      
      pub fn eat_at_restaurant() {
          // Absolute path
          crate::front_of_house::hosting::add_to_waitlist();
      
          // Relative path
          front_of_house::hosting::add_to_waitlist();
      }
    • 其他 mod 声明的模块里的内容,是当前模块内容的 child

      mod front_of_house
  • pub 修饰的 struct,各个字段仍然是 private。pub 修饰的 enum,各个自动默认是 public。

Use Keyword

  • use 使用的惯例:Bringing Paths Into Scope with the use Keyword - The Rust Programming Language (rust-lang.org)

  • 在当前项目使用 mod 声明的模块, 也有 use 的作用,并且不能重复 use,会报错重复导入

  • Re-exporting: Bringing Paths Into Scope with the use Keyword - The Rust Programming Language (rust-lang.org). 不过 use 一个模块A,并不会默认包含这个模块A中使用 pub use 声明的其他模块B,只是可以通过模块 A::B 来引用。

  • 一种导入多个内容的简写方法

    • 例: use std::{cmp::Ordering, io};
    • 导入模块自身使用 self,例:use std::io::{self, Write};
  • 一个模块风格问题,后面的风格存在问题:The main downside to the style that uses files named mod.rs is that your project can end up with many files named mod.rs, which can get confusing when you have them open in your editor at the same time.

    • src/front_of_house.rs (what we covered)
    • src/front_of_house/mod.rs (older style, still supported path)

Common Collections

  • Collections 中的数据都存在堆(heap)上

Vector

  • Vector 的 push 方法会对在当前 vector 中的每一个数据创建一个 mutable reference,这个规则和重新分配空间有关: Vectors put the values next to each other in memory, adding a new element onto the end of the vector might require allocating new memory and copying the old elements to the new space。下面的代码会报错,String.push_str() 方法也会发生 borrow。

    let mut v = vec![1, 2, 3, 4, 5];
    let first = &v[0]; // immutable borrow occurs here
    v.push(6); // mutable borrow occurs here
    println!("The first element is: {}", first);
  • vector 只能存相同类型的数据,但是 enum 的各个 variant 属于同一个 enum 类型,因此可以通过 enum 实现存不同类型的值。

    enum SpreadsheetCell {
      Int(i32),
      Float(f64),
      Text(String),
    }
    
    let row = vec![
      SpreadsheetCell::Int(3),
      SpreadsheetCell::Text(String::from("blue")),
      SpreadsheetCell::Float(10.12),
    ];
  • Dropping a Vector Drops Its Elements: Like any other struct, a vector is freed when it goes out of scope.

String

  • 一个字符串调用 method 丢掉 ownership 例子:String.into_bytes(self) 方法会得到实例的 ownership (which moves s2)

    let mut s = String::from("😀");
    let vec = s.into_bytes(); // `s` moved due to this method call
    println!("{s}"); // Error: value borrowed here after move
  • 提到一个概念:deref coercion 这个机制使下面的例子编译不报错

    let s1 = String::from("Hello, ");
    let s2 = String::from("world!");
    let s3 = s1 + &s2;
    // fn add(self, s: &str) -> String { ... }
    // 注意 s 的类型是 &str,但是实参传的是 &String 类型
  • 一个 macro 介绍:format!:

    • Work like println!, but instead of printing the output to the screen, it returns a String with the contents.
    • Use references so that this call doesn’t take ownership of any of its parameters.
  • rust 解释字符串的三种方式:as bytes, scalar values, and grapheme clusters (the closest thing to what we would call letters).

  • 字符串切片(slice)是以字节为单位,如果结尾不是合法的 UTF-8 边界,会报错(panic)

    let hello = "Здравствуйте"; // 字符串里的 3 现在也是 Unicode scalar value,占 2 个字节。单独出现的 "3" 占一个字节(根据 UTF-8 编码规则)
    let s = &hello[0..1];

HashMap

  • HashMap 实例的 insert 方法
    • 会尝试拿到 ownership:For types that implement the Copy trait, like i32, the values are copied into the hash map. For owned values like String, the values will be moved and the hash map will be the owner of those values
    • 传引用时 Map 不会拿到 ownership:If we insert references to values into the hash map, the values won’t be moved into the hash map. The values that the references point to must be valid for at least as long as the hash map is valid.
  • HashMap 使用 SipHash,特点是更慢但能防止 DoS 攻击,可以使用实现了 BuildHasher trait 的类型自定义哈希函数。

Generic Types, Traits, and Lifetimes

Generic Type

  • Rust 的泛型参数也是用<>定义在各种结构的名字后面。在使用函数泛型,struct 泛型,enum 泛型的时候,不需要显式给出类型,编译器会自己推断。也可以显示给出类型,例:

    impl Point<f32> {
        fn distance_from_origin(&self) -> f32 {
            (self.x.powi(2) + self.y.powi(2)).sqrt()
        }
    }
    /*
    This code means the type Point<f32> will have a distance_from_origin method; other instances of Point<T> where T is not of type f32 will not have this method defined.
    */
  • 函数泛型的一个例子

    fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
        let mut largest = &list[0];
    
        for item in list {
            if item > largest {
                largest = item;
            }
        }
    
        largest
    }
  • 结构泛型的一个例子

    struct Point<T, U> {
        x: T,
        y: U,
    }
    
    fn main() {
        let both_integer = Point { x: 5, y: 10 };
        let both_float = Point { x: 1.0, y: 4.0 };
        let integer_and_float = Point { x: 5, y: 4.0 };
    }
  • 枚举泛型的一个例子

    fn plus_one(x: Option<i32>) -> Option<i32> {
      match x {
        None => { None }
        Some(i) => {
          Some(i + 1)
        }
      }
    }
    
    let five = Some(5);
    let six = plus_one(five);
    let none = plus_one(None);
  • Impl 后跟 :By declaring T as a generic type after impl, Rust can identify that the type in the angle brackets in Point is a generic type rather than a concrete type. We could have chosen a different name for this generic parameter than the generic parameter declared in the struct definition, but using the same name is conventional.

    struct Point<T> {
        x: T,
        y: T,
    }
    
    impl<T> Point<T> {
        fn x(&self) -> &T {
            &self.x
        }
    }
  • Struct 泛型实现的方法可以有不同的泛型参数

    struct Point<X1, Y1> {
        x: X1,
        y: Y1,
    }
    
    impl<X1, Y1> Point<X1, Y1> {
        fn mixup<X2, Y2>(self, other: Point<X2, Y2>) -> Point<X1, Y2> {
            Point {
                x: self.x,
                y: other.y,
            }
        }
    }
    
    fn main() {
        let p1 = Point { x: 5, y: 10.4 };
        let p2 = Point { x: "Hello", y: 'c' };
    
        let p3 = p1.mixup(p2);
    
        println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
    }

Trait

  • Trait 有点像接口

  • 实现 trait 的一个限制 —— orphan rule / coherence: We can’t implement external traits on external types.

  • Trait 的方法可以有默认实现,也可以覆写。但是在覆写的方法中不能调用默认实现。(不能类比子类调用父类被 override 的方法)

  • Trait 可以限制函数参数类型,函数返回类型

  • 有个很好用的特性 blanket implementations: We can also conditionally implement a trait for any type that implements another trait. 注意 for 后跟的是泛型参数 T。

    impl<T: Display> ToString for T {
        // --snip--
    }

lifetime

基础概念

  • 当函数的返回值是引用类型时,就需要考虑函数参数和返回值的生命周期,否则不用考虑。因为生命周期的目标为防止虚引用(Dangling References),所以这个行为是和函数返回引用相关的。同样,引用类型才有必要标生命周期,例如 &'a str,非引用类型没有标生命周期的语法。

  • 当 longest 被调用时,生命周期 'a 会被替换成 x 和 y 中较短的生命周期(重叠的部分),而不是限制 x 和 y 的生命周期要完全一致。因此返回值的生命周期不能比 x 和 y 中较短的生命周期长。

    fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
      if x.len() > y.len() {
        x
      } else {
        y
      }
    }
  • 关于 struct 中使用 lifetime: This annotation means an instance of ImportantExcerpt can’t outlive the reference it holds in its part field.

    struct ImportantExcerpt<'a> {
        part: &'a str,
    }
  • lifetime elision rules 可以让部分情况省略生命周期,遵循三个规则:

    • The first rule is that the compiler assigns a lifetime parameter to each parameter that’s a reference. In other words, a function with one parameter gets one lifetime parameter: fn foo<'a>(x: &'a i32); a function with two parameters gets two separate lifetime parameters: fn foo<'a, 'b>(x: &'a i32, y: &'b i32); and so on.

    • The second rule is that, if there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters: fn foo<'a>(x: &'a i32) -> &'a i32.

    • The third rule is that, if there are multiple input lifetime parameters, but one of them is &self or &mut self because this is a method, the lifetime of self is assigned to all output lifetime parameters. This third rule makes methods much nicer to read and write because fewer symbols are necessary.

  • 两个概念:Lifetimes on function or method parameters are called input lifetimes, and lifetimes on return values are called output lifetimes.

String literals 的生命周期

String literals 的生命周期是 Static Lifetime: Live for the entire duration of the program.

// 下面的代码报错
let string1 = String::from("long string is long");
let result;
{
  let string2 = String::from("xyz");
  result = longest(string1.as_str(), string2.as_str()); // `string2` does not live long enough
}
println!("The longest string is {}", result);

// 下面的代码不会报错
let string1 = String::from("abcd");
let result;
{
  let string2 = "xyz"; // All string literals have the 'static lifetime. 

  result = longest(string1.as_str(), string2);
}
println!("The longest string is {}", result);

Smart Pointers

Box<T> 手动在堆上分配数据

  • 使用 Box<T> 时数据在堆上,指针在栈上。当指针超出 scope 时,栈上的指针和堆上的数据同时释放(deallocated)。堆上的数据被释放是因为 Box 实现了 Deref trait。对实现了 Deref 的实例做解引用(dereference)时, *y*(y.deref()) 的语法糖。

  • 一个概念 indirection, 和指针类似: means that instead of storing a value directly, we should change the data structure to store the value indirectly by storing a pointer to the value instead.

  • 实现一个 trait 的时候,impl 的大括号 {} 里不能实现非 trait 定义的方法。所以下面代码里 new 方法要分开实现。

    struct MyBox<T>(T);
    
    impl<T> MyBox<T> {
      fn new(x: T) -> MyBox<T> {
        MyBox(x)
      }
    }
    
    impl<T> Deref for MyBox<T> {
      type Target = T;
    
      fn deref(&self) -> &Self::Target {
        &self.0
      }
    }
  • 一个概念 Deref coercion

    • 解释:Converts a reference to a type that implements the Deref trait into a reference to another type. For example, converting &String to &str because String implements the Deref trait such that it returns &str.

    • 条件: It happens automatically when we pass a reference to a particular type’s value as an argument to a function or method that doesn’t match the parameter type in the function or method definition. 如果把一个引用(reference)当函数参数传参并且类型和形参不匹配,并且这个引用指向的类型实现了 Deref trait,就会尝试 Deref coercion。严谨表达的条件是:

      • From &T to &U when T: Deref<Target=U>
      • From &mut T to &mut U when T: DerefMut<Target=U>
      • From &mut T to &U when T: Deref<Target=U>
    • 一个 Deref coercion 例子,用上面代码的 MyBox 实现

      let m = MyBox::new(String::from("Rust"));
      fn hello(name: &str) {
        println!("hello, {name}");
      }
      
      hello(&m); // &m 类型为 &MyBox<String>, 自动 deref 后变为 &String. 第二次,&String, 自动 deref 后变为 &str
      hello(&(*m)[..]) // 和 deref coercion 等价的手动操作

Drop Trait

  • Drop trait 类似析构函数,不过触发的时机不同:In Rust, you can specify that a particular bit of code be run whenever a value goes out of scope, and the compiler will insert this code automatically. Drop trait 的 drop 方法也被成为 destrcutor.
  • 变量执行 drop 方法的顺序:Variables are dropped in the reverse order of their creation, so d was dropped before c.
  • 可以主动调用 std::mem::drop 函数,这个函数自动在作用域(在 prelude 里),执行的方法仍然是 Drop trait 中实现的 drop 方法,而且对一个变量主动调用 std::mem::drop 之后,变量离开作用域时不再自动调用 drop 方法。

Rc Trait

  • Rc<T>::clone 只拷贝引用: The call to Rc::clone only increments the reference count, which doesn’t take much time. Deep copies of data can take a lot of time.
  • Rc<T>::clone 只能拷贝不可变引用(immutable reference),Via immutable references, Rc<T> allows you to share data between multiple parts of your program for reading only.

RefCell, Reference circle

  • (挖坑)

Macros - The Rust Programming Language (rust-lang.org)

  • 定义:Macros are a way of writing code that writes other code, which is known as metaprogramming

  • rust 是在编译时实现 macro 机制 : Macros are expanded before the compiler interprets the meaning of the code, so a macro can, for example, implement a trait on a given type.

  • rust 没有类似 Java 的反射机制,但还是有类似注解的功能,随处可见的 #[] 可以类比 Java 的注解。除了声明宏(declarative macros),其他类型的宏都有 #[] 的用法,这些宏在实现具体逻辑时会接受类型为 TokenStream 的参数,使用 syn crate 可以解析 TokenStream 类型的数据,再进一步处理。一个 Derive 宏接收的 TokenStream 参数通过 syn crate 解析的例子:

    DeriveInput {
        // --snip--
    
        ident: Ident {
            ident: "Pancakes",
            span: #0 bytes(95..103)
        },
        data: Struct(
            DataStruct {
                struct_token: Struct,
                fields: Unit,
                semi_token: Some(
                    Semi
                )
            }
        )
    }

其他 Misc

  • 使用本地依赖的方式

    • 文件夹结构
    hello_macro
       ├── hello_macro_derive
       │    ├── src
       │    │    ├── lib.rs
       │    └── Cargo.toml
       └── pancakes
       │    ├── src
       │    │    ├── main.rs
       │    └── Cargo.toml
       │
       └── src
       │    ├── lib.rs
       │
       └── Cargo.toml
    • hello_macro/Cargo.toml
    [dependencies]
    hello_macro = { path = "../hello_macro" }
    hello_macro_derive = { path = "../hello_macro/hello_macro_derive" }

使用 Clash 和 Charles 同时科学上网和抓包

TLDR:

解决前:HTTP/HTTPS/SOCKS Request -> Clash(127.0.0.1:7890)
解决后:HTTP/HTTPS/SOCKS Request -> Charles(127.0.0.1:8888) --charles转发--> Clash(127.0.0.1:7890)

How to

  • 打开 clash,打开 charles
  • charles -> Proxy -> External Proxy Settings -> 设置 charles 转发规则,把转发端口设到 Clash 代理端口

image

可能容易忽略的正则表达式优先级(

  • 大多数编程语言或语法系统的操作符都有优先级(一个反例, Lisp 没有操作符优先级),正则表达式也有这样的机制。

    • 转义符 \ 的优先级最高,或 | 的优先级最低。
    • 除转义符 \ 外,括号类 (), (?:), (?=), [] 的优先级最高。除或操作符 | 外,任何 literal 字符的优先级最低(空格也是 literal 字符)。
    • 由于 | 的优先级最低,因此 /red|blue car/g 匹配 red 或者 blue car。使用括号提升优先级后,/(red|blue) car/g 匹配 red car 或者 blue car
  • [] 内有特殊含义的字符有 -, ^, \。除此之外,在 [] 内的字符都只是 literally 匹配其本身。

    1. 横杠 - :用于指定字符范围。例如,[a-z] 表示匹配任何小写字母。
    2. 插入符号 ^ :用于表示否定。例如,[^a-z] 表示匹配任何不是小写字母的字符。
    3. 转义符 \: 用来转义特殊字符,在 [] 里转义 -, ^, \
    4. 因此,[](, ) 不会转义, [(red)|(blue)] car 的匹配结果是下图。[(red)|(blue)] 实际匹配 ( r e d | b l u ) 这些字符中的一个。

    image-20230219103248151

  • Lookaround 用 () 包裹表达式,但不保存分组。语法可以概括为 (?[前后]<匹配/不匹配><正常的正则语法>)。向前(ahead)指文字前进方向。

    • 向前匹配使用 (?=xxx),向后匹配使用 (?<=xxx)。向前不匹配使用 (?!xxx),向后不匹配使用 (?<!xxx)。
  • 一些常见用法/特殊字符整理

    • . 除换行符的任何字符
    • \R 匹配任何 Unicode 换行符序列,兼容CRLF、CR 、LF 换行符。但有的浏览器不支持 \R
    • [\s\S], [\w\W], [\d\D]:匹配任何字符,包括换行符。集合和自身的补集构成全集。
  • 应用

    • 不考虑换行,查以 xxx 开始,以 xxxx 结尾的字符串。

    image-20230219110721237

    • 查找大写字母开头单词前的空格

    image-20230219112914224

Git refs

A ref is an indirect way of referring to a commit. You can think of it as a user-friendly alias for a commit hash. This is Git’s internal mechanism of representing branches and tags.

refs 文件夹下面的文件内容是 commitId,默认情况下 some-feature 分支会被解析为 refs/heads/some-feature,refs/heads 文件夹下是本地分支的文件,另外还有 remote(远程仓库分支),tags(git 标签)文件夹,里面的每个文件都记录一个 commitId。

image

SSH config file

ssh 的配置文件路径为 ~/.ssh/config,可以为每个主机地址(可以用域名标识)设置 ssh 使用的用户名,端口号,私钥文件位置。一个用处是当使用 ssh 给 github 推代码,但是 github 的 22 端口报连接失败时,可以把端口号改成 443

Host GitHub
 Hostname ssh.github.com
 Port 443

另外 ProxyJump 可以配置 ssh 走跳板机:

tokio 的 task 饥饿问题

  • 每个 worker thread 对应一个 OS 线程,启动一个事件循环,不断地从任务队列里取出 task 执行。
  • 每个 task 是一个协程 / 绿色线程,对 task 的调度没有线程优先级等概念,但是开销更小。
  • 一个执行非常耗时或者不会结束的 task 会占住一个 worker thread关于 worker thread 数量的讨论。当 worker thread 被占满之后,新的 task 就不会执行了。
use tokio;

#[tokio::main]
async fn main() {
    // let rt = tokio::runtime::Runtime::new().unwrap();
    let mut handles = vec![];
    for i in 0..100 {
        let handle = tokio::spawn(async move {
            // tokio::time::sleep(std::time::Duration::from_secs(10000)).await; // tokio 会用计时器优化,执行到这里会从当前的绿色线程让出执行权给 worker thread(即 OS 的线程),不会导致其他线程饥饿
            let data = Mutex::new(0);
            let _d1 = data.lock();
            let _d2 = data.lock(); // 死锁
            println!("task {} finished", i);
        });
        handles.push(handle);
    }
    handles.push(tokio::spawn(async {
        println!("a very simple task"); // 不会执行
    }));
    futures::future::join_all(handles).await;
    println!("main finished");
}

Async tokio task debugging using tokio-console

Example

#[tokio::main]
async fn main() {
    console_subscriber::init();
    for _ in 0..10 {
        tokio::spawn(async {
            for i in 0..100 {
                sleep(Duration::from_secs(1)).await;
                println!("inside {i}");
            }
        });
    }
}

How to run

1. Install dependencies

Add project dependencies in Cargo.toml

console-subscriber = "0.1.0"

Install CLI Tool

cargo install tokio-console

2. Run the above program with tokio_unstable FLAG

RUSTFLAGS="--cfg tokio_unstable" cargo run --bin tokio-console

3. use tokio-console CLI to connect

In terminal using tokio-console

Screenshot

image image

Reference

https://github.com/tokio-rs/console

开发工具(长期更新)

  1. iterm2
    https://iterm2.com/
  2. zsh,支持非常多的插件,支持 tab 补全和命令简写,插件在 ~/.zshrc 里配置,下载好的插件位置在 ~/.oh-my-zsh/plugins/
    https://github.com/ohmyzsh/ohmyzsh
  3. fig,非常强的终端提示工具,甚至有 UI
    https://fig.io/
  4. tldr 常用命令解释,例:tldr git add
    https://github.com/tldr-pages/tldr

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.