💡 Sharing some engineering experiences that are not quite summarized elsewhere. They are put at issues.
notebook's Introduction
notebook's People
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
-
User triggered (ctrl+c/right click copy). tip: Can only be triggered by the user
-
triggered by document.execCommand('copy'). tip: Compatibility。
-
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); });
- 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 handlerhandleCopy(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 astring
, so it is easy to copy and paste across applications using the agreed-upon MIME type. Value is astring
type, usually JSON serialized data.types
holds all the key,getData()
,setData()
operations key, value. w3c's demo -
Use
new DataTransfer()
to constructclipboardData
cannot associate it with memory. So the key to copy is to get theclipboardData
, to achieve this, usedocument.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) 画面是黑屏,不能控制
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::spawn
和tokio::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 runtimestd::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 expression30 * scale
and, becausedbg!
returns ownership of the expression’s value, thewidth
field will get the same value as if we didn’t have thedbg!
call there. We don’t wantdbg!
to take ownership ofrect1
, so we use a reference torect1
in the next call.
Method 引入
- &self 是 self: &Self 的简写:Methods must have a parameter named
self
of typeSelf
for their first parameter, so Rust lets you abbreviate this with only the nameself
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: theString::from
function that’s defined on theString
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 namedcrate
.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_house
是eat_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 aString
with the contents. - Use references so that this call doesn’t take ownership of any of its parameters.
- Work like
-
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, likei32
, the values are copied into the hash map. For owned values likeString
, 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.
- 会尝试拿到 ownership:For types that implement the
- 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 afterimpl
, Rust can identify that the type in the angle brackets inPoint
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 itspart
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 ofself
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
becauseString
implements theDeref
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
whenT: Deref<Target=U>
- From
&mut T
to&mut U
whenT: DerefMut<Target=U>
- From
&mut T
to&U
whenT: Deref<Target=U>
- From
-
一个 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, sod
was dropped beforec
. - 可以主动调用
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 设置终端代理的讨论
-
设置代理
clash 默认代理的 https, http 端口为 7890,设置终端代理(以zsh shell 为例):
echo 'export https_proxy=http://127.0.0.1:7890/ http_proxy=http://127.0.0.1:7890/ all_proxy=socks5://127.0.0.1:7890' > ~/.zshrc
-
测试是否代理成功
ping 使用的是 ICMP 协议,不支持代理。
你可以执行 curl -vv https://www.google.com 看看有没有走代理。
Electron 浅尝
使用 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 代理端口
-
charles -> Proxy -> macos Proxy -> 查看
charles 代理端口
,默认是 8888 -
改一下系统的HTTP, HTTPS, SOCKS 代理,端口是
charles 代理端口
-
macos:https://support.apple.com/zh-cn/guide/mac-help/mchlp25912/mac
-
windows:
选择“开始” 按钮,然后依次选择“设置” >“网络和 Internet”>“代理”
-
-
Done,现在所有的 HTTP,HTTPS,SOCKS 流量都会首先经过 Charles,然后 Charles 转发给 Clash,注意这时候的 Clash -> 设置为系统代理 应该没有打钩(系统代理是 Charles)
可能容易忽略的正则表达式优先级(
-
大多数编程语言或语法系统的操作符都有优先级(一个反例, Lisp 没有操作符优先级),正则表达式也有这样的机制。
- 转义符
\
的优先级最高,或|
的优先级最低。 - 除转义符
\
外,括号类(), (?:), (?=), []
的优先级最高。除或操作符|
外,任何 literal 字符的优先级最低(空格也是 literal 字符)。 - 由于
|
的优先级最低,因此/red|blue car/g
匹配red
或者blue car
。使用括号提升优先级后,/(red|blue) car/g
匹配red car
或者blue car
。
- 转义符
-
[]
内有特殊含义的字符有-, ^, \
。除此之外,在[]
内的字符都只是 literally 匹配其本身。- 横杠 - :用于指定字符范围。例如,
[a-z]
表示匹配任何小写字母。 - 插入符号 ^ :用于表示否定。例如,
[^a-z]
表示匹配任何不是小写字母的字符。 - 转义符
\
: 用来转义特殊字符,在[]
里转义-, ^, \
。 - 因此,
[]
里(
,)
不会转义,[(red)|(blue)] car
的匹配结果是下图。[(red)|(blue)]
实际匹配( r e d | b l u )
这些字符中的一个。
- 横杠 - :用于指定字符范围。例如,
-
Lookaround 用
()
包裹表达式,但不保存分组。语法可以概括为(?[前后]<匹配/不匹配><正常的正则语法>)
。向前(ahead)指文字前进方向。- 向前匹配使用
(?=xxx)
,向后匹配使用(?<=xxx)
。向前不匹配使用(?!xxx)
,向后不匹配使用(?<!xxx)。
- 向前匹配使用
-
一些常见用法/特殊字符整理
.
除换行符的任何字符\R
匹配任何 Unicode 换行符序列,兼容CRLF、CR 、LF 换行符。但有的浏览器不支持\R
。[\s\S]
,[\w\W]
,[\d\D]
:匹配任何字符,包括换行符。集合和自身的补集构成全集。
-
应用
- 不考虑换行,查以 xxx 开始,以 xxxx 结尾的字符串。
- 查找大写字母开头单词前的空格
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。
SSH config file
ssh 的配置文件路径为 ~/.ssh/config
,可以为每个主机地址(可以用域名标识)设置 ssh 使用的用户名,端口号,私钥文件位置。一个用处是当使用 ssh 给 github 推代码,但是 github 的 22 端口报连接失败时,可以把端口号改成 443
Host GitHub
Hostname ssh.github.com
Port 443
另外 ProxyJump 可以配置 ssh 走跳板机:
浏览器相关
- Dispatching event,事件派发过程,非常非常详细,踩到相关的坑可以在这里找原因
https://dom.spec.whatwg.org/#dispatching-events - 可以给 DOM 元素加任意属性,再拿到对应的属性
Can I add arbitrary properties to DOM objects?
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
Reference
开发工具(长期更新)
-
将 JetBrains 快捷键应用到VSCode的插件, 因为 JetBrains 各个 IDE 快捷键都一样,所以下面这个 Intellij 其实适用所有 JetBrains 的 IDE,而且比 VSCode 插件市场里叫 JetBrains IDE keymap 插件支持的快捷键全的多
https://marketplace.visualstudio.com/items?itemName=k--kato.intellij-idea-keybindings -
mac 下 terminal 增强
- iterm2
https://iterm2.com/ - zsh,支持非常多的插件,支持 tab 补全和命令简写,插件在 ~/.zshrc 里配置,下载好的插件位置在 ~/.oh-my-zsh/plugins/
https://github.com/ohmyzsh/ohmyzsh - fig,非常强的终端提示工具,甚至有 UI
https://fig.io/ - tldr 常用命令解释,例:
tldr git add
https://github.com/tldr-pages/tldr
- 群友推荐的一个 neovim 插件,支持重构 tql
https://github.com/cshuaimin/ssr.nvim
三层交换机和路由器比较
三层交换机和路由器都是三层设备,但是三层交换机的路由表更小,通过集成电路 ASICs(Application-specific integrated circuit) 实现硬件转发,吞吐量更高。
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.