明经CAD社区

 找回密码
 注册

QQ登录

只需一步,快速开始

搜索
查看: 675|回复: 2

[其它] 法务+编码味道+链式调用+Rust生命周期

[复制链接]
发表于 2024-9-7 10:33:26 | 显示全部楼层 |阅读模式
本帖最后由 你有种再说一遍 于 2024-9-13 08:41 编辑

法务+编码味道+链式调用+Rust生命周期
# 法务
有时候很讨厌几家科技大公司的函数名不一样,细查之后,发现它们多数是因为法务原因而避让.
之前Sum(甲骨文)公司把Google起诉了,理由是安卓用了JavaSE API,直接复制了0.4%的声明代码,后来裁决了API相同并不构成版权问题,Sum败诉.
美国是同时存在判例法和阐释指定法,在没有判例之前是不存在指导案例的,所以这就考验了对于律师和法官的演绎推理能力了.
此外还有一种叫净室开发的方式来绕过侵权问题,即提供相同功能,也不构成侵犯知识产权,但是可能没有专利权.
净室开发是第一组人员进行功能分析,然后告诉第二组人员进行开发,使得代码层面自然就不一样了.获取的方式当然要合法啦,而且获取方式居然可以是已有产品API说明书.

# 编码味道
有时候编码的味道是很重要的,
像avlisp就缺少很多味道,动不动就遍历啦,没有命名空间啦,跟写汇编和c语言似的,每个指令都记忆,关键是没有函数提示...
而c++/c#语言的class.Method()可以在.的位置进行提示然后自动补全,这样编辑器就很好做.

那么有没有为了IDE写一个语言呢?
真有,那就是Rust.大神Linus让写驱动层的过渡去Rust,内核层则不然.

Java很多地方都map map的,因为它是一个很重要的味道.
大家有没有想过,结构和map其实是一回事呢?
你想想class.A和struct.A和map["A"]它们不就是一个占用空间和寻址的区别而已嘛?本质上都是KeyValue.
那么寻址足够快,map是不是相当于一个动态的结构体了,这就是为什么Java要优化这个底层,采用位运算指令周期少,而取余的周期高.
class和struct:
http://bbs.mjtd.com/thread-191057-1-1.html
JDK1.8的hashmap:
http://bbs.mjtd.com/thread-190705-1-1.html

# 链式调用
Google三驾马车之一的MapReduce模式多出名,它在js代码示例如下:
```JavaScript
const array = [1, 2, 3, 4];
const result = array
  .map(x => x * 2) //每个元素乘2
  .filter(x => x > 2) //过滤,获取大于2的元素
  .reduce((acc, x) => acc + x); //聚合,求和
```

c#利用linq实现,函数名有点不同:
```csharp
var array = new List<int> { 1, 2, 3, 4 };
int result = array
  .Select(x => x * 2) //每个元素乘2
  .Where(x => x > 2) //过滤,获取大于2的元素
  .Aggregate(0,(acc,x)=>acc+x); //聚合,求和
```

js的reduce==c#linq的Aggregate,
但是推荐c#末尾直接用.Sum(),因为Sum/Average/Max/Min这些函数在net8.0自带SIMD.
注意不是Sum(x => x + 1)这种选择器模式,不然可能没有SIMD的(看了源码https://segmentfault.com/a/1190000042837291真没有,不知道存不存在通过编译器优化,然后函数内联之类,大概率不会,毕竟没那么智能)

所以会简化为:
```c#
var array = new List<int> { 1, 2, 3, 4 };
int result = array
  .Select(x => x * 2) //每个元素乘2
  .Where(x => x > 2) //过滤,获取大于2的元素
  .Sum(); //全部求和
```

js和c#差异是:
在js的map和filter方法会立即创建新的数组,而c#linq仅仅是表达式树,所以就可以看得出来它们设计哲学的不同.
js占用运行内存高,并且中间对象多,大量的数组会进入GC回收,它强调是不可变性和纯函数.
c#在大数据上面比较快,它少了很多中间对象,不会引发cpu boom.java的stream也是如此.

编译器生成的是一系列条件语句进行链式判断,此处是伪代码示意:
```txt
c#生成表达式树示意:
public int MapReduce(int[] array) {
  int num = 0;
  for(int j = 0; j < array.Length; j++){
    int x = array[j]*2;
    if(x > 2) { num += x}
  }
  return num;
}

js编译器生成示意:
public int MapReduce(int[] array) {
  List<int> s1 = new();
  for(int j = 0; j < array.Length; j++) {
    s1.Add(array[j]*2);
  }
  // s1将从动态数组转为不可变数组传递下去
  List<int> s2 = new();
  for(int k = 0; j < s1.Count; k++) {
    if( s1[k]>2 ){ s2.Add(s1[k]); }
  }
  // s2将从动态数组转为不可变数组传递下去
  int num = 0;
  for(int m = 0; j < s2.Count; m++) {
    num += s2[m];
  }
  return num;
}
```

你们想知道链式调用怎么做的吗?
```csharp
// 定义一个类
class Test {
  int _a = 0;
  public Test Add(int num) {
      _a += num;
      return this;//返回自身实现链式调用
  }
  public void Print() {
      Console.WriteLine(_a);
  }
}
// 调用:
var t = new Test();
t.Add(1).Add(2).Add(99).Print();
```


# Rust特性
## 引用和指针
C++结构体和类这样传参,写拷贝副本的.
C#倒是结构体拷贝副本,类不拷贝.
```cpp
class MyClass {
  //..
};
void function(MyClass obj) {
  // 'obj' 是 'MyClass' 对象的一个副本
}
```

所以需要这样:
1.0 通过引用传递Pass by Reference:
```cpp
void function(MyClass& obj) {
  // 'obj' 是对原始对象的引用
}
// 调用:
MyClass myObject;
function(myObject); // 传引用
```

2.0 通过指针传递Pass by Pointer:
```cpp
void function(MyClass* obj) {
    // 'obj' 是指向原始对象的指针
}
// 调用:
MyClass myObject;
function(&myObject); // 传指针
```

C/C++经常说,引用是指针的别名,那么你会感觉"既生瑜何生亮",如果总是感觉它们一样,那可能是接触面太少,领悟的规则不够多.

学习Rust可以让你深入了解引用和指针的区别.
Rust是一种"过作用域必然释放"的语言.
它诞生了所有权转移机制,无论如何你都要具备所有权的知识,其中最主要是:变量的不可变性/生命周期/借用规则.
通过生命周期标注对齐生命长度,生命周期是为了避免悬垂引用,从而释放内存.
编译器做巨量的工作在编码阶段就检查代码了,即使你不会也必须要会了才能写出来,不然编译不通过.

## 简单比较
在C#中生命周期是淡化的,只发生在大括号.
```csharp
public static void main1 () {
  { int a = 1; }
  int b = a;//报错,无法访问
}
public static void main2 () {
  string s = "string";
  string r = "";
  { r = s; }
  Print(r);//顺利赋值并打印
}
```

同样例子,所有权转移,这里会产生报错:
错误例子1:悬垂引用错误
```rust
fn main() {
    let s = String::from("hello");
    let r;
    {
        let t = &s;  // 借用,不报错
        r = t;          // 引用赋值(非copy类型-数值类型),产生所有权转移
    } // 悬垂引用: 超出作用域,丢弃了t,之后r就成为悬垂引用,相当于一个尸体,没有装入灵魂.
    println!("Length of `r`: {}", r.len()); // 这里尝试使用r,将会导致编译错误.
}
```

怎么改才是对的呢?
```rust
// 方案1: 初始化后赋值
// 这样打印出来是对的,就是充满垃圾代码...并且得到一系列警告(不是错误).
fn main() {
    let s = String::from("hello");
    let ra = String::from("xxx"); // 初始化
    let mut r = &ra; // 借用
    {
        let t = &s;  // 借用
        r = t;          // 赋值,对s的引用
    }
    println!("Length of `r1`: {}", r.len());
}

// 方案2: 套一层函数返回引用,多了一层栈帧,但是可以通过内联进行消除.
fn main() {
    let s = String::from("hello");
    let r = get_reference(&s);
    println!("Length of `r2`: {}", r.len());
}

#[inline(always)] // 积极内联
// 拒绝内联是#[inline(never)]
fn get_reference(s: &String) -> &String {
    let t = s;
    t
}

// 方案3: 通过克隆副本,多了内存开销.
fn main() {
    let s = String::from("hello");
    let r;
    {
        let t = &s;
        r = t.clone();
    }
    println!("Length of `r3`: {}", r.len());
}

// 方案4: 可变引用
fn main() {
    let mut s = String::from("hello");
    {
        let r = &mut s;  // 可变借用
        println!("Length of `s41`: {}", r.len());  // 可以读取长度
        r.push('k');  // 可以改变长度,因为r是可变引用
    }  // r 离开作用域
    println!("Length of `s42`: {}", s.len());  // 可以读取长度
}

// 接着才是绕过Rust的安全检查机制:指针
// 方案5:
fn main() {
  let s = String::from("hello");
  let p: *const String = &s;
  // 如果你尝试解引用裸指针,就需要使用 unsafe 块
  unsafe {
      println!("Length of `s5`: {}", (*p).len());
  }
}

// 方案6:
使用std::mem::forget函数可以用来防止值的自动清理,这在某些情况下可以用来避免悬垂引用,但这种做法非常危险,因为它可能会导致内存泄漏.
use std::mem;
fn main() {
    let s = String::from("hello");
    let p = &s as *const String;
    // 这里使用 unsafe 块来解引用裸指针,并且使用 forget 来防止 s 被自动清理
    mem::forget(s);
    unsafe {
        println!("Length of `s6`: {}", (*p).len());
    }
}
```

## 生命周期长度标注
例子1:
str是字面量,&它是引用类型,函数只有一个参数,那么编译器可以自动推导生命周期:
```rust
fn print_book_title(book: &str) {
    println!("书名是:{}", book);
}
fn main() {
    let book_title = "Rust程序设计语言";
    print_book_title(book_title);
}
```

例子2:
函数有2+参数就不行,因为存在生命长度不一,不知道怎么释放,要用生命周期符号约束起来,包括返回值:
```rust
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}
fn main() {
    let string1 = String::from("long string");
    { // 短的作用域
    let result = longest(string1.as_str(), "short");   
    println!("The longest string is {}", result);
    }
}
```

例子3:
结构体字段的引用也要保证声明周期一致.
```rust
// 结构体,
// 约束两个字段生命周期为'a,表示共同长度
struct Book<'a> {
    title: &'a str,
    author: &'a str,
}

// 定义一个函数
// 参数接收Book实例和生命周期参数'a
// 返回字符串:书名和作者名,它不是引用所以不标注生命周期
fn describe_book<'a>(book: &'a Book<'a>) -> String {
    // 末尾最后一行自动返回参数
    format!("'{}' by {}", book.title, book.author)
}

fn main() {
    // 栈帧字符串字面量,作为Book实例的字段.
    let title = "标题";
    let author = "作者";
    // 栈帧创建一个Book实例
    let book = Book { title: title, author: author };
    // 调用函数并打印返回的描述
    println!("{}", describe_book(&book));
}
```
这种例子太多了,大家可以自行学习
(完)

评分

参与人数 1明经币 +1 收起 理由
tranque + 1

查看全部评分

发表于 2024-9-7 10:48:09 | 显示全部楼层
感谢惊惊大佬的分享,在整理整理可以出书了
发表于 2024-12-18 16:45:52 | 显示全部楼层
跟大佬学习到了!
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

小黑屋|手机版|CAD论坛|CAD教程|CAD下载|联系我们|关于明经|明经通道 ( 粤ICP备05003914号 )  
©2000-2023 明经通道 版权所有 本站代码,在未取得本站及作者授权的情况下,不得用于商业用途

GMT+8, 2024-12-27 10:58 , Processed in 0.158992 second(s), 23 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表