Rust会取代C++吗?

文摘   2024-11-26 10:54   美国  

点击上方蓝字 江湖评谈设为关注/星标




前言

自从Rust出道以来,备受关注。美国DARPA(国防部高级研究计划局)推动了一项计划,把不安全的C/C++代码转换成安全的Rust。因为嫌弃人工转换太慢,这个过程后续会在AI的加持下,自动化的加速大规模转换,抛弃旧有体系下陈旧的C/C++技术。美国作为当今计算机互联网的基石国家,这种转换是否意味着C/C++的没落呢。

新旧交替,实是世界变化理。唯一的不变,就是永远不断变化的事物。

多线程

多线程的安全一直困扰着C++,对于Rust来说这自然是小菜一碟,不过现代化的C++也在增强这方面的处理过程。

C++:

int counter = 0;void unsafe_increment() {    for (int i = 0; i < 100; ++i) {        ++counter; // 未原子化优先级,数据竞争    }}int main() {    std::vector<std::thread> threads;    for (int i = 0; i < 10; ++i) {        threads.emplace_back(unsafe_increment);    }    for (auto& t : threads) {        t.join();    }    std::cout << "Final counter value: " << counter << std::endl; // 结果不确定    return 0;}

以上代码是一个典型的多线程共享变量忘记了加锁,或者没有对多线程的共享变量进行原子化操作。emplace_back每次创建一个新线程,执行unsafe_increment函数,后者则在里面自增循环了counter变量100次。

以上代码的运行情况导致了counter自增结果的不可预测性,很容易出问题。确代码的应该怎么做呢针对safe_increment函数,可有如下改进。

  1. 添加一个全局锁,当counter循环自增的时候,添加锁保证线程访问的同步。这里用的是lock_guard 是RAII(资源获取即初始化)风格的代码。

std::mutex mtx;void safe_increment() {    for (int i = 0; i < 100; ++i) {        std::lock_guard<std::mutex> lock(mtx); // 加锁        ++counter;    }}

2.声明counter为原子化的类型,保证每次的递增安全

std::atomic<int> counter{0};void atomic_increment() {    for (int i = 0; i < 100; ++i) {        ++counter; // 原子操作    }}

Rust

那么针对以上C++代码,Rust怎么处理这个问题呢?非常简单

use std::sync::{Arc, Mutex};use std::thread;fn main() {    let counter = Arc::new(Mutex::new(0));    let mut handles = vec![];    for _ in 0..10 {        let counter = Arc::clone(&counter);        let handle = thread::spawn(move || {            for _ in 0..100 {                let mut num = counter.lock().unwrap();                *num += 1;            }        });        handles.push(handle);    }    for handle in handles {        handle.join().unwrap();    }    println!("Final counter value: {}", *counter.lock().unwrap());}

Rust声明了一个线程安全的引用计数智能指针Arc,一个互斥锁Mutex,一个Arc指针克隆,一个lock锁的访问,再加上编译期间的错误检查能力。完全锁死了counter的自增结果,确保其万无一失的线程访问同步自增。既不会遗忘,也不会有出错的概率。

悬空指针和双重释放

悬空指针和双重释放是C++困扰多年的问题,Windows的高危漏洞暴露在MSF(msfconsole)上有近百个是这两个问题造出的。

C++悬空指针和双重释放:

#include <iostream>void dangling_pointer() {    int* ptr = new int(42); // 动态分配内存    delete ptr;            // 释放内存    std::cout << *ptr << std::endl; // 未定义行为,访问悬空指针}void double_free() {    int* ptr = new int(42);    delete ptr; // 第一次释放    delete ptr; // 第二次释放,未定义行为}int main() {    dangling_pointer();    double_free();    return 0;}

Rust:

fn main() {    let x = Box::new(10); // Box 智能指针,负责堆内存分配    // 内存自动管理,无需手动释放    println!("{}", x);    // 离开作用域,内存自动释放,无法引发悬空指针或双重释放}

Rust/C++优缺点对比

Rust比C++至少有以下优点

  1. Rust比C++拥有更现代化的工具链,诸如:Cargo,Clippy,Rustfmt极大的提高了代码开发效率,质量和节省时间。而C++工具分散,Win/Linux/MacOS个一套自己的开发工具,号称跨平台的Cmake可阅读性非常的之差,兼容性也是一言难尽,难以忍受。

  2. Rust内置标准包的管理和强大的社区生态,Rust 自带的Cargo和Crates.io生态系统,让开发者能够方便地引入高质量的开源库。C++则缺乏官方包管理工具,依赖管理通常由第三方工具(如Conan或vcpkg)解决,各个平台,各个厂商,各个系统五花八门,眼花缭乱。开发者的大部分精力都放在处理这些乱七八糟的东西上面了。

  3. Rust有更好的错误处理机制,Rust 提供了类型化的错误处理(ResultOption),让错误变得显式且易于处理。编译器强制处理潜在的错误。明确错误的传播(通过?操作符)。C++错误处理通常依赖异常或返回码,容易被忽视,且难以阅读和查找理解。

结尾

C++垂垂老矣,近年来频繁添加的各种特性越来越向带GC的语言和Rust擅长的编译期间的检查特性靠拢了。比如constexpr提供编译期计算能力,优化性能,类似于Rust。比如std::shared_ptr:提供引用计数,与 Java和C#的对象引用类似,在最后一个引用离开作用域时自动释放内存。

但个人依旧认为离C++退场还有一段距离,不过这个距离应该不是太远了。

往期精彩回顾

NET9典型优化例子IV

Go-main linux内核细节收尾

 C++安全指针,Rust用处何在?


江湖评谈
记录,分享,自由。
 最新文章