终于来到了 Rust 的精髓所在了,那就是使之不依赖于垃圾回收又能保障内存安全且高效运行的所有权系统(Ownership System)。想要用 Rust 做一个稍显规模项目必定绕不过它,所有权系统包括所有权(Ownership), 借用(Borrowing), 生命周期(Lifetimes)。
以下概念的复述基本是从 《Rust编程: 入门, 实战与进阶》一书中而来,那里面有些内容是来自于官方的 The Rust Programming Languge - Understanding Ownership.所有权系统的基本概念
Rust 的编程语法很快就能上手,让学习 Rust 曲线陡然大增的也就是这个所有权系统。所有权检测在编译期完成,Rust 能编译出来的代码就是安全高效的。要理解 Rust 的所有权系统必须首先明白以下两组概念:- 栈内存(Stack),值语义(Value Semantic),按位复制(浅复制)(Shallow Copy),复制语义(Copy Semantic)
- 堆内存(Heap), 引用语义(Reference Semantic), 深复制(Deep Copy),移动语义(Move Semantic), 借用(Borrowing)
和其他语言一样,大小固定的所有基本类型都可以存储在栈上,栈上存取数据总是在栈顶操作,很快,而访问堆内存需要搜索内存地址。所有权系统的主要任务是用来跟踪堆上的数据,即引用语义的数据。 Read More
试手了一下 Rust, 发现止今所学知识尚浅,不少情况处理起来很是茫然,比如经常得到的值是 Result 或 Option, 有时候要 .unwrap(), 有时得后面加个问号,或 .await。还有从自定义函数里返回一个引用都不容易,到处是 move, borrow, owner, lifetime 之类的错误信息。所以说,快充五分钟还真不敢上路,继续学习 Rust 的 Result, 说起它又必须与 Rust 的错误处理联系起来。
编程语言在处理错误无外乎就两种哲学- 得到正确的结果或触发异常,异常不就地处理则向外传播
- 总是能得到一个结果,于结果中获知是否有异常,如 Option<T>, Result<T, E>
第二种方式,在 shell 或 C++ 很常见,比如 shell 下每调用一个命令都有返回数值, 即 $?, 0 为正常,非 0 为有错误; C++ 的 GetLastError() 也是一样的意思。
现在所学的 Rust 在处理结果和错误时,采用了第二种方式,不过使用上 Rust 的模式匹配处理起 Option<T>, Result<T, E> 倒也不难,而且 Option<T> 和 Result<T, E> 这样的返回结果还用来向上传播异常,出错时还能追踪到异常栈。 Read More
引用与解除引用
觉得还是有必要继续深入学习一下 Rust 再练手,毕竟仍然看到 & 和 * 符号还有些恍惚,大概就是 C/C++ 里的取地址和取值操作吧,实际上也确实类似。只是叫法略有不同, 还有就是在 C/C++ 多用了指针的概念。
在 C/C++ 中,&:称作 Address-of Operator, 在 Rust 中称作 Reference Operator, 而*在 C/C++ 和 Rust 中都叫做 Dereference Operator。以前学 C/C++ 经常被一系列的 &, * 打晕了头,如今参考了它们的英文名称立刻变得清晰了起来。
就像当初看汇编各种寻址方式弄得头都大了,其实也就是依照约定。 Read More
再继续快速学习一下 Rust 的以下几个知识点,就可以开始着手做点小工具了- 基本数据类型
- 复合数据类型
- 基本的流程控制
元组
元组和 Python 的元组用法类似,Immutable, 可混合类型1let tup1 = (10, 7.2, 'a'); 2let tup2 = (100, ); // 一个元素时,和 Python 一样,后面附加逗号,否则视括号可选 3let tup3: (i8, f32, boo) = (-10, 7.7, false); // 类型要一一对应 4let tup4: () = (); // 声明一个空元组,元组是不可变的,所以没什么意义 5 6let (x, y, z) = tup1; // 元组的拆解 7 8println!("{}", tup1.0); // 访问用 .index 方式访问数组
数组类型中的元素类型相同,表示为 [T; n], T 为类型,n 为元素个数 Read More
学了不少编程语言,多数是离不开垃圾回收的,要么像 C++ 仍然是过于复杂,对于通用,编译型编程语言 Rust 是个不错的选择, Rust 不需要垃圾回收器。Rust 是由 Mozilla 主导开发的,设计准则为 "安全,并发,实用", 支持函数式,并发式,过程式以及面向对象的编程风格。Rust 能达到与 C++ 接近的性能,它又是编译型语言,编译出来二进制文件执行时不再依赖于运行时。Rust 有自带的 Cargo 作为依赖管理与构建工具,免除了关键工具的选择综合症。AWS 在今年也推出了 Rust 的 AWS SDK, 所以学习 Rust 的过程中也打算使用它来操作 AWS 的资源。Rust 的 Hello World
在 macOS 下的安装$ brew install rust
或$ curl https://sh.rustup.rs | sh # 安装 $ rustc --version # 查看版本 $ rustup update # 更新 $ rustup self uninstall # 卸载
当前 Rust 版本为 1.74.0, 安装后有 rustc, rustdoc, rust-gdb, rust-lldb, cargo 等相关命令 Read More- 本书的阅读又搁置了许久,虽然感觉 Manning 出版社的这一
100 Mistakes系列从书的质量不是那么的高,但开了头还是继续从本书 40% 的位置往下。
开始要讲述到异常了,异常还是有必要认真对待的,比如- Java 中很容易被 CheckedException 弄得代码不整洁
- 缺少必要的参数检查,不舍得抛出异常,视异常为 Bug
- 不明确出现异常时后续如何处理,
- 或者是捕获而隐藏了异常致使定位错误变得更难。
Java 的主要异常大分类是Throwable
NullPointerException, 这恐怕是一个最常见的异常,Java 对一个对象是否能为 null 值没什么约束,甚至用 null 来表示业务上的空。比如说方法的参数与返回值,Java 都可以是 null 值,而在 Kotlin 中非明确可为 null 的时不能为 null Read More
├── Error
└── Exception
└── RuntimeException
不觉一晃还是在五年前记录过一篇 Dockerfile 中命令的两种书写方式的区别,其中提到过 Dockerfile 中可选择用 ENTRYPOINT 或 CMD 来启动进程,并且在 ENTRYPOINT 和 CMD 都支持 exec, shell 和增强型 shell 方式。如果同时有 ENTRYPOINT 和 CMD(或 docker 运行时的 CMD), 则 CMD 将为 ENTRYPOINT 提供参数。
在原来那篇文中认为 shell 无法接收到 docker stop 或 docker -s SIGTERM 发来的信号,也许是随着 Docker 版本的变迁,Docker 变得越发聪明了起来,无论何种格式的 ENTRYPOINT, 都能够收到 SIGTERM 信号,比如在 Java 的 ShutdownHook 能捕捉到该信号,得以在进程停止之前作必要的清理工作。
进行本文相关研究的主因是部署在 ECS(Fargate) 中的 Java Web 服务,Task 总是因为 OutOfMemoryError 被杀掉,而在应用程序日志中却见不着半点线索说 JVM 的 OutOfMemoryError,即使后来给 Fargate 配上了 EFS, 加了+XX:+HeapDumpOnOutOfMemoryError XX:HeapDumpPath=/efsJVM参数,在任务被 kill 时在 /efs 上从来就都没生成过内存映像文件。最后发现是因为 JVM 的 -Xmx 配置太高,留给 Fargate 容器的太少的缘故。 Read More
突然间研究这个来的缘由是正在从 Jenkins 往 Harness 的过度, 而完全用命令来构建 Docker 镜像变得不一样了。在 Jenkins 中 Agent 本身也是一个 Docker Daemon, 所以 Docker 命令执行无障碍,而 Harness 的所谓的 Agent 就是一个个的运行在 Kubernetes 中的 Docker Container (Pod) 了,这其中没有 Docker Daemon, 又不能连接到 Kubernetes 本身的 Docker Daemon。另外 CloudBees CI/CD 的运行环境与 Harness 类似,也是运行在 Kubernetes 中的 Pod。
因此可能要使用某个 Docker 容器来作为 Docker Daemon, 所以牵连出对此的研究,相应的方案有 Docker in Docker(DinD) 和 Docker outside of Docker(DooD)。
对容器中启动 Docker Daemon 的探索
在知晓 DinD 和 DooD 这两个概念本人还试图构建过一个 Docker 镜像,试图用一个 Docker 容器既作 Docker Daemon 又作为 Docker 客户端。在容器中 Docker 安装成功,但无法在容器中启动 Docker Daemon。比如用下面的 Dockerfile Read More
继续阅读本书,编程语言处理数值都有可能出现问题,如溢出,整数的最大最小值不对称,Double.NaN 等。
由于 Java 学了 C,也用 0 开始的数字来表示 8 进制数,如 037, 010 分别是十进制的 31 和 8,这与现实不相符。因为如果你在纸上写下 037, 010, 几乎所有人(除了某些程序员)都会认为它们就是十进制的 37 和 10。但是 Java 表示 2 进制, 16 进制的方式没有问题的,如 0b10, 0x37。IntelliJ IDEA 看到使用 0 开头的 8 进制数会不建议那么使用. 8 进制数字的范围是 0~8, 所以 09 是错误的, 但是 Java 编译器似乎对此很陌生.
int a = 09;
IntelliJ IDEA 会提示Integer number too large, 编译器提示说java: ';' expected, 有点驴唇不对马嘴.
现在几乎没有必要使用 0 开始的 8 进制数的方式, 或许还有用的就是表示 Unix 下文件权限, 如
int fileMode = 0644
所以任何时候看到 0 开头的数字都必须仔细检视, 基本可以禁止使用这种方式 Read More
这几日在阅读 Manning 出版社的 《100 Java Mistakes and How to Avoid Them》, 其中列举的确实是一些容易带入到代码中的错误,不少还是通过代码 Review 或单元测试很难发现的问题。也有些看似很弱智,却可能是隐匿许久的定时炸弹,只等某一特定条件出现时即爆。
阅读的同时简单的作了笔记及少许联想,所以内容有些杂乱无条理。最前面介绍了一些静态代码分析工具,也有两个动态分析工具。本书目前还是 Manning 的 MEAP 体验版,未正式发售。一共讲了 100 个常见错误如何避免(例如,怎么用最新 Java(Java 9 -- Java 21) 语法, API 来改进),以及用静态分析工具,单元测试及早发现。
这是读完了 1/4 数量的记录,笔记开始 Read More