Rust反射和访问规则

405 阅读25分钟

本文翻译自 fractalfir.github.io/generated_h…

反射是很多人希望Rust语言拥有的东西:很容易偶然发现一些人有一个有趣的用例。

人们想用它来实现序列化、GC、更好的互操作等等。

如果你能想到一个任务,就有人希望他们能使用反射来实现它。

不幸的是,它似乎不会很快到来。

尽管如此,像“这个功能还不存在”这样的愚蠢的事情不能阻止我们谈论它如何能够甚至必须工作。

反射以一种有点违反直觉的方式与Rust的安全功能交互。这些交互会强制任何反射API遵守某些规则。

这些限制对于设计反射API的人或Rust专家来说似乎是显而易见的,但它们似乎很少被提及。

本文将尝试用简单易懂的语言来解释这些规则。我不仅会展示事情是如何工作的,而且我还会解释为什么这些要求存在。

此外,我将探讨这种限制的连锁反应-主要围绕访问字段。您可能会惊讶于这些对潜在反射API的形状影响有多大。

字段访问规则

在许多语言中,反射能够访问对象的所有字段,无论它们是否是私有的。反射只是似乎有点特殊,并能够弯曲的规则在这里和那里。

当然,这有一些限制,但是,一般来说,关于反射的规则往往不那么严格。

由于它来自一种具有反射的高级语言,因此似乎可以合理地假设这是反射的一个自然部分。你可以在C#中设置私有字段的值:

class Test {
  private float _secret;
}
void SetSecret(Test test, float val) {
  typeof (Test).GetField("_secret", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(test, val);
}Copy

  在Java中:

class Test {
private float _secret;
}
class Spy {
void SetSecret(Test test, float val) {
   Field field = getField(test.getClass(), “_secret”);
   // Oh, this field is inaccessible?
    field.setAccessible(true);
   // Now it is accessible.
    field.set(test, val);
 }
}Copy

这在其他语言中甚至更容易,比如python或lua;在那里,隐私的概念更多的是一种礼貌而不是规则。

class TestClass:
 __secret"Spooky"
# No privacy in snake-land...
setattr(a, '_TestClass__secret',val)
# This works too :)
test._TestClass__secret = valCopy
test.secret = val;
-- In Lua, everything is just a table.
test["secret"] = val;Copy

这样做通常被视为一种反模式,因为它破坏了封装。然而,它在某些情况下是有用的;例如,当序列化和重新序列化数据时。毕竟,要求所有可序列化/可重序列化字段都是公共的可能会带来更多的麻烦。

你可能会问:那又怎样?是的,在许多现代语言中,反射都可以访问私有字段,但它与Rust有什么关系呢?

好吧,在Rust中事情不能这样工作,不管最终实现了什么反射API。让我告诉你为什么。

反证法

由于反射并不是一件容易理解的事情,您可能认为显示反射问题的示例也很复杂。

值得庆幸的是,它可以用一些广泛使用的Rust类型来演示,比如Box<i32>-这意味着你不需要太多的知识来理解事情是如何工作的。

让我们假设我们有某种方法可以使用反射访问所有字段,并且我们使用假设的API来编写这样的函数:

/// Gets a mutable reference to the first field of a struct using reflection
fn get_first_field_mut<T,F>(t:&mut T)->&mut F{
// Implemented using reflection
}Copy

乍一看,这似乎并不那么可怕。除了获取对(潜在的)私有字段的可变引用之外,它没有做太多事情。

在其他语言中,访问私有字段被认为是必要之恶。当然,如果你太...你的同事可能想让你成为过去式。不过,只要您有一个非常好的理由像这样使用反射,并且有节制地使用它,一切都会好起来的。

在Rust中,这是根本无法做到的--允许反射访问私有字段从根本上说是不安全的。

怎么做?让我给你看看。

Bad Boxes 坏盒子

使用这个看似无辜的函数,我们可以写这样的代码:

// A `Box<i32>` points to a heap-allocated integer
let boxed = Box::new(1_i32);
// Walk through internal types of `Box`, to find its internal pointer
let unique = get_first_field_mut(&mut boxed);
let non_null = get_first_field_mut(unique);
let raw_ptr:&mut *const i32 = get_first_field_mut(non_null);
// After getting the internal pointer of `Box<i32>`, we replace it with an invalid one.
*raw_ptr = 0xDEAD_BEEF_usize as *const i32;
// The box now points to an invalid memory location, so trying to read its value is UB
// and may cause our program to crash
let val:i32 = *boxed;Copy

这可能有点难以理解,所以跟着我。

你可能知道,Box类型允许我们在堆上存储数据。在Box中,我们可以找到指向此数据的指针。

Rust Box由几层类型组成(*TNonNull<T>Unique<T>Box<T>),每一层都告诉我们更多关于指针的信息。

在这里,我们使用反射来遍历所有这些类型,并替换该指针(例如。0x7dff_3340)和垃圾值(0xDEAD_BEEF)。

现在,盒子“认为”它的值在一些不相关的地址。当我们试图获取该值时,我们将导致未定义行为(UB)。

根据内存位置上的具体内容,我们的程序可能会崩溃,或者读取一些不相关的数据。

这有效地破坏了内存安全,因此根据定义,它在安全的Rust中是不可能的。

在这一点上,我认为您也可以确切地看到为什么安全反射API不允许这种情况发生。因此,Rust中的反射必须遵守字段访问规则。

在其他语言中,违反这些规则会使你的程序难以阅读。在Rust中,这些规则对于执行安全性是严格必要的。打破他们打破了整个语言,这是不太理想的。

因此,Rust中的反射必须尊重访问规则(也称为“字段隐私”)或不安全

你可能马上会有几个问题。

  1. 这个问题似乎是由我们创建了一个对私有字段的可变引用引起的。不可变引用是否也有类似的问题?
  2. 此问题由指针引起。禁止在反射中访问指针会有帮助吗?
  3. 有那么严重吗?反正也没人会写这样的代码?
  4. 为什么反射甚至需要访问私有字段?如果不能进入私有领域,反思的局限性会是什么?

所有这些都很重要,所以让我们从前几个开始。

内部可变性

起初,我们可能需要可变访问来引起任何麻烦。毕竟,如果我们不能改变底层数据,我们就不能真正造成任何伤害,不是吗?所以你可能会认为对反射的这种限制应该足够了。

好吧,我们仍然可以使用“不可变”引用来造成相当大的危害。虽然大多数Rust类型在没有获得可变引用的情况下无法更改,但其中一些可以。因此,允许任何形式的对私有字段的意外访问都是一种灾难。

考虑Arc的引用计数。计数器是一个AtomicUSize-一种可以改变的类型,而不需要可变引用。例如,如果我们可以将它的计数器设置为1,当它应该更高时,我们可能会导致释放后使用的错误。

let a = Arc::new(7);
let b = a.clone();
let count = get_arc_count_via_reflection(&a);
count.store(1, Ordering::Relaxed);
// Frees the underlying arc - b still points to the arc, and is now dangling!
drop(a);
// Reads freed memory - UB, and may lead to crashes
let val = *b;Copy

这个例子还说明了第二个问题:我们需要担心的不仅仅是指针。

不安全整数

更改几乎任何私人数据都可能导致问题。考虑一个Vec的长度-它只不过是一个普通的整数(usize)。

let mut two_strs = vec!["Hello","world!"];
let len:&mut usize = get_vec_len_via_reflection(&mut two_strs);
// Corrupt the vec len:
*len = 1024;
// Try printing the elements of the vec - this is UB and will most likely cause a crash
for s in two_strs{
println!("s:{s}");
}Copy

通过改变一个简单的整数,我们已经造成了很大的损害。

这希望表明,访问私有字段的反射可能会导致问题。

你可能会问:这些问题现实吗?是的,允许反射访问私有字段可能会导致不安全,但在实践中会这样吗?

对这个问题最简单但有点无聊的回答是,这并不重要。

即使某些东西“不太可能”导致UB,它在Rust中仍然是不安全的。

因此,如果通过反射访问私有字段可能导致UB,那么根据定义,它是不安全的。

安全的反射API不允许这种情况发生。

诚然,这个答案有点技术性。我不喜欢仅仅展示理论上的不安全,而是更喜欢给出更具体的例子。

不是所有的东西都是数字

许多C库给出整数,表示对象的不透明句柄。因此,一个句柄OpenGL纹理可能只是Rust端一个整数的简单包装。

/// An OpenGL texture
struct Texture(u32);Copy

如果有人试图序列化这样的类型(使用反射),就会发生糟糕的事情:句柄只在当前线程内有效。因此,有些人可能认为他们正在保存底层图像数据,但会有一个令人讨厌的惊喜。

// Save the result of rendering a frame to the disk
reflect_serialize(out_file, render_result);Copy

上面的代码片段乍一看并没有错,但它会导致崩溃。

这是我们绝对要避免的。

Rust是一种专注于安全性的语言,要求人们知道什么可以安全地序列化,什么不能安全地序列化,这与此并不吻合。

限制反射

到了这一点,你可能已经了解了事情的大致要点。Rust中的反射无法安全地访问私有字段。我想很多人也认为允许反射访问私有字段通常是一个坏主意。所以,这里没有太多损失。

尽管如此,说某事“不可能,也是一个坏主意”感觉有点虎头蛇尾。当然,一定有办法既能吃蛋糕又能拥有蛋糕。允许反射访问私有字段可以说是有用的:许多语言都使用反射来实现序列化。

理想情况下,我们不希望强制所有可序列化类型都只有公共字段。这可能会引起更多的麻烦比它的价值。

反射标记

您可能会想:好吧,我们不能允许反射访问所有私有字段,因为其中一些字段可能不安全。我们仍然可能希望它访问一些我们希望保持私有的字段。

也许我们可以实现一个可见性标记,允许反射访问这些字段?类似于:

struct MostlyPrivate{
 /// Normally private, but accessible via reflection
pub(reflect) id:u32,
}Copy

至少在我看来,这是合理的。我们明确选择反射,清楚地标记哪些字段可以更改,哪些字段不能更改。我们仍然可以得到一些隐私的好处。这些字段不能从“正常”(人类编写的)代码中访问,因此不太可能有人偶然访问它们。

另外,我们可以在将来扩展这种语法,以允许更多地控制哪个crate可以通过反射访问这些字段。

struct MostlyPrivate{
 /// Normally private, but accessible via reflection
pub(reflect(serde, some_other_crate)) id:u32,
}Copy

很难衡量这种语法的冗长是否值得,但它仍然是值得考虑的。

总的来说,这个解决方案并不完美。添加这些反射标记使我们能够更好地控制我们暴露或不暴露的字段。这迫使我们认识到潜在的问题,并围绕它们开展工作。

这种额外的控制也是这个想法的最大缺点。这将需要crate作者的思想发生一些变化,他们必须显式地为其类型添加反射支持。

不安全字段

或者,我们可以考虑从不安全字段特性中窃取一些想法。

struct FloatAndEven{
/// Safety: must be even!
// Invisible to reflection, since it is unsafe
unsafe even:i32,
// Accessible via reflection, since it is not unsafe
 float:f32,
}Copy

如果我们假设任何不安全的领域都可以安全地反映出来,那么也许事情会解决?这感觉有点脆弱(人们可能会忘记将字段标记为不安全),并且在该功能稳定之前编写的代码无法工作。

我也不相信混合反射访问和字段安全是一个好主意。

稍后我会谈到这一点,但是改变任何可以通过反射访问的东西都可能是一个突破性的变化。

struct MyType{
 float:f32,
// This new field is safe, but private
// Right now, adding it is *not a breaking change*
// With reflection, external code may stop working after it is added.
 debug_descr:&'static str,
}Copy

这有什么大不了的难说.

尽管如此,我觉得这个解决方案是为了完整起见应该提到的。

多米诺骨牌效应:这些限制如何影响反射

至此,您可能明白了为什么Rust中的反射必须遵守字段访问规则。我想大多数人都会同意,允许反思打破它们通常是一个坏主意。所以,我不认为任何人会对这种“限制”太生气。

到目前为止,我看到的大多数提案都是这样做的,所以这并不是一个大发现。尽管如此,我还是喜欢在解释某种行为的后果之前解释为什么某种行为是某种方式。既然这已经不碍事了,我们可以开始讨论一些更有趣的东西:连锁效应。

想一想,当反思过程中出现问题时,应该发生什么。你不能像这样安全地将一个类型私有化:

struct NotOk{
// Private field reflection can’t access
 private:u32,
}Copy

反射不能访问它的某些字段,因此不能用于非线性化。这让我问一个简单的问题:接下来会发生什么?很明显的答案是,这应该会导致编译器错误,但是.

为什么反射需要自定义边界

举一个例子,让我们假设我们创建了一个序列化函数,如下所示:

pub fn serialize<T: /* What should go there???*/>(t:&T, out: &mut impl Write)->SerializationResult{
/* Implemented using reflection*/
}Copy

我们应该如何表达基于反射的序列化的界限?要使用反射序列化一个类型,我们需要它满足一组重要的标准。

它的所有字段都需要通过反射来访问每个字段类型也必须是可序列化的,即:一个原始类型,例如:a:i32一个具有显式序列化实现的类型,例如:a. B:Box<i32>也满足我们的标准的类型c:Somewhere SerializableType因此,出现了一个简单的问题:我们应该如何表达这些复杂的边界?

一个没有界限的世界

一个想法可能是......根本不添加任何此类界限?如果我们的基于反射的序列化框架错误地给出了一个描述性消息,那么这个绑定是不是有点多余?

error[E1234]: reflection error
  --> my_type.rs:32:64
   |
32 | pub(crate) not_serializable:*mut i32,
   | -^^^^- This field cannot be accessed via reflection
   |  |
   |  type MyType can’t be serialized because of this field
   |
   = note: Error during compile-time reflection
note: this error was caused by a reflection-based function called here
  --> test_serialization.rs:128:256
   |
128| serialize(&my_type, &mut out_file).unwrap();
   |   ^^^^Copy

很遗憾,不是。考虑一个更复杂的例子。谁说有问题的函数是直接调用的?例如,错误可能源于网络框架的深层,该框架确保数据在网络上保持同步。

在这种情况下,编译器应该发出一个回溯吗?

reflection_serializer::serialize<MyType>
network_framework::SharedData<MyType>::send
network_framework::SharedData<MyType>::sync
network_framework::SharedData<MyType>::new
my_crate::my_codeCopy

感觉有点...低于标准。编译器错误包含像这样的回溯的想法似乎非常麻烦。

这些信息中的大多数与根本问题并不相关。

随着反射变得越来越先进,它可能失败的方式也越来越多。我们绝对不希望任何人使用一个板条箱来筛选我们的代码,以找出出了什么问题。

如果我们对表示反射边界有一些支持,那么错误会更清楚。我们只是没有满足顶级函数的边界。

error[E1234]: reflection bound not satisfied
  --> my_type.rs:32:64
   |
32 | let score = SharedData::new(MyType::default());
   |                     -^^^^- This call requires `MyType` to fulfill the reflection bound
   |                       |
   |                      type MyType does not fulfill the `reflection_serializer::is_serializable` bound
   |Copy

此外,如果没有明确的界限,有人可能会在不知道他们调用的函数的所有要求的情况下编写代码。

fn log<T>(t:&T){
reflection_serialize(LOG_FILE.lock().unwrap(),t);
}Copy

如果只这个函数的签名,很可能会认为它可以记录任何类型,而不仅仅是可序列化的类型。

从后来的一些编译器错误中了解这个要求将是一种误解。

哦,这个愚蠢的函数告诉我它可以处理任何类型,然后连续工作20次,但是现在,它决定了......不想工作了吗

我想你会同意,所有这些问题都会让你的开发人员体验变得更糟。

因此,由于反射可以“失败”(例如。无法访问所需的所有数据),我们需要以理智的方式处理这一点。

处理反射“故障”

明确我们的界限似乎是最合理和用户友好的解决方案。如果我们可以这样做:

fn serialize<T:SerializableViaReflection>(t:&T, out:&mut impl Write->SerializationResult{ /**/}Copy

然后,所有使用这种基于反射的序列化框架的crate也可以透明地表达这种需求。

// In network_framework
fn send<T:reflection_serializer::SerializableViaReflection>(t:T){ /**/}Copy

感觉好多了…但是我们怎么这样的一个约束呢

这可不容易

我们需要这些“反射界限”非常灵活,因为人们希望将反射用于各种目的。我们不想限制它的用例。检查一个类型是否是可序列化的,在GC中遍历一个类型的“子”,确保正确的互操作布局--所有这些都有非常不同的要求

计算类型布局不需要对类型的字段进行任何访问-它需要检查这些字段在哪里,以及它们的类型是什么。**

#[repr(C)]
struct Interop{
// All of those fields are private - but we don't care.
// All that matters is their offset, and type.
 natural:c_int,
 f_point:float,
}Copy

为了实现GC而遍历一个类型的子类型并不关心私有字段--只要这些字段不是GC句柄。

struct MyGCType{
// This field needs to be accessible for the GC to work
pub object:Option<GC<MyGCType>>,
// but we don't care about this one:
 number:f32,
}Copy

另一方面,序列化类型要求其所有字段都是可访问的。

struct SomeType{
// OK - accessible to reflection
pub i:i32,
// Not OK - not accessible to reflection
pub u:u32,
}Copy

所有这些情况都大不相同。对于更复杂的用例(例如,blitting数据),这些要求可以变得更加具体。我们仍然需要一些方法来支持所有这些

我们的边界也需要非常 非常精确。我们不希望有任何类型满足边界,但随后导致反射出错。这就从一开始就破坏了拥有它们的意义。

理想情况下,它们至少应该相对容易编写。

这是一个很大的要求...我们有什么选择?

我们如何表达如此复杂的边界?

您的直觉可能会添加一些简单的机制来检查通用条件是否对所有字段都成立。

然而,这可能并不像人们想象的那么简单。

正如我提到的,这些要求可能会有很大的不同,可能没有一个一刀切的解决方案。

我们还有其他选择吗?

这里的一个想法可能是也使用反射来表达这些需求。

我们可以试试这样的:

const fn is_serializable<T>()->bool{
/* Do our complex checks using reflection*/
}
// Only valid if is_serializable<T>() returns true
fn serialize<T>(t:&T, out:impl Writewhere True<{is_serializable::<T>()}>Copy

在这个场景中,我们使用反射来表达反射所需的边界。这种方法的好处是非常灵活。我们不受可能执行的检查类型的限制,并且我们可以在编译时确保一些非常复杂的安全不变量,这很简洁。

此外,由于我们使用反射来表达我们的需求,我们可以从我们编写的原始代码中获得灵感。我们可以将绑定与实现进行比较,并通过这种方式发现任何潜在的不匹配。

然而,再一次,这种方法并非没有其自身的缺点。编写这些边界并不简单,而且会引入相当多的样板文件。然而,一般来说,反思并不容易,所以有人可能会说,这在这里并不重要。很难说是否存在更简单的方法来表达这些界限。

在我看来,更大的缺点是自我指涉类型的问题。请看下面这个例子:

struct IsThisThingSerializable{
 normal_field:u32,
 recursive:Option<Box<Self>>,
}Copy

检查这件事是否满足我们复杂的反射需求并不是小事。如果此类型的所有字段(包括递归字段)都可以序列化,则该类型可以序列化。要确定该字段是否可以序列化,我们需要检查是否...所讨论的原始类型可以被序列化。

使用递归编写的简单方法会很快陷入循环。

有办法解决这个问题,但它们也不容易理解。可能存在某种框架,使这个过程更容易,但我仍然觉得反射的复杂性也在这里抬起了丑陋的头。

总的来说,这似乎又一次让Rust中实现反射变得更加棘手。

安全与不安全的反思

在这一点上回到反射的安全性似乎有点奇怪,但我想在文章结束之前留给您一些需要考虑的东西。

到目前为止,我或多或少认为反思一定是安全的。如果访问私有字段会导致问题,那么简单地禁止它似乎是合理的。这就是我所知道的大多数反思提案所做的事情。这是一个干净、整洁的解决方案,尽管更有限。

对于这个问题,还有一个替代的解决方案:使有问题的反射API不安全,但仍然提供它们。我们可以为有需要的人提供更深入、更低层次的访问权限,并为其他人提供安全、更受限制的替代方案。

是否应该存在不安全的反射API?

一方面,通过更低级别的访问暴露不安全的API并不是什么新鲜事。这个关键字的存在是有原因的:它允许我们做编译器无法证明安全的事情。使用unsafe并不一定是“错误的”或“不好的”--它只是意味着我们100%负责确保我们的代码正确。这感觉就像是两全其美:我们得到了安全强大的反射。有什么不喜欢的呢?

SemVer

嗯,事情不是那么简单。允许访问私有字段会泄露实现细节,并可能破坏版本控制。即使API不安全,它也不应该在不同版本之间中断。尽管如此,人们可能会争辩说,添加任何类型的反射都会影响版本控制,至少会有一点点。例如,向类型添加一个新字段,如下所示:

#[non_exhaustive]
pub struct Foo {
pub f1: i32,
}Copy

目前还不是一个突破性的变化。然而,由于它将是可见的反射,它可能成为一个突破性的变化。

Social effects 社会效果

我们还必须考虑到,如果不安全的反射API比安全的反射强大得多,那么它可能成为首选。人们可能会听到:“哦,用安全反射来做这件事有点棘手”,然后就用不安全的变体。不安全代码的困难不在于你做什么,而在于你不能做什么。因此,拥有不安全的反射API可能会导致人们使用它们编写大量不合理的代码。

什么是和不是实现细节?

另一个重要的问题是:我们是否应该允许安全反射来获取关于私有字段的信息?有时,当我们编写一些互操作/不安全代码时,我们可能需要仔细检查我们正在处理的数据的布局。这样的检查可以省去我们的头痛,并允许我们更自信地编写代码。尽管如此,它仍然会暴露实现细节,这些细节可能会在crate版本之间发生变化。这可能会把微小的变化变成破坏性的变化。私人领域的变化将成为外部可观察的,有人可以依赖它。

我认为在编译时破坏代码比运行时错误更好,但这仍然是需要考虑的问题。

“安全”反射有多有用?

我认为我们必须回答的最大的问题是:没有访问私有字段的反射有多大用处?它将如何影响我们编写的代码?

如果我们希望一个类型通过反射是可序列化的,我们必须使它的所有字段都可以在我们的crate之外访问。该类型的定义变得公开可见,不再只是实现细节。我们无法控制我们的数据如何以及在何处被更改。

struct MyType{
// We *intend* to allow the serialization framework to access those fields,
// but anybody can change them using reflection.
pub(reflect) a:i32,
pub(reflect) b:i32,
// Adding any new field - even one that *does not end up being serialized*
// is a breaking change
 f: Option<SomeDebugInfo>,
}Copy

使用proc-macros,我们可以安全地访问私有字段(在派生的宏实现中)。我们还可以完全控制哪段代码可以访问这些字段。这种额外的控制可以防止很多问题。

#[derive(Serialize,Deserialize)]
struct MyType{
// Accessible only to the serialization crate we use.
 a:i32,
 b:i32,
// Not a breaking change - not publicly accessible,
// and does not end up serialized.
#[do_not_serialize]
 f: Option<SomeDebugInfo>,
}Copy

如果反射最终比proc-macros更受限制,更不可靠,那么我们可能看不到将其添加到语言中的所有好处。

它可以更快(取决于实现),但我认为大多数人希望反射可以做得更多,而不是更少

我相信,这是Rust中反射的最大挑战:它需要小心地绕过这些潜在的陷阱。

它需要找到一些完美的地方,在那里它弯曲规则足以适应大多数用例,同时不破坏任何东西。

如何才能实现这种微妙的平衡?

老实说... 我不知道

未知领域

离开这篇文章而不给出我所讨论的问题的一个简洁的解决方案,我感到有点奇怪。

然而,我认为这是最重要的一点:我们有点处于未知的沃茨。以前在其他语言中也实现过反射,但是... Rust有点独特。

这是一种首先注重安全的语言。Rust有一种非常不寻常的内存管理方法-所有这些都改变了反射API的工作方式。

如果内存安全是通过类型系统强制执行的,那么反射也需要适应它。具体怎么做这很难肯定地说。

说实话,这篇文章是我自己反思工作的结果。我已经尝试了很多不同的想法来杀死这个野兽的一个功能,与.结果好坏参半。

一次又一次,我觉得自己陷入了同样的潜在问题,尽管使用了截然不同的方法。

有时候,我觉得试图给Rust添加反射就像试图用太小的桌布覆盖一张桌子:你总是会牺牲一些东西。我的一些想法非常强大,用户友好,但最终不安全。其他人是安全的,但需要编写更多的样板文件,并且没有涵盖我和无数其他乡村人想到的所有用例。

随着2024年即将结束,我意识到:我不需要解决所有这些问题。

我是一个完美主义者,陷入了一个非常简单的陷阱:我不想写任何关于反思的东西,直到我把一切都弄清楚。然而,让我的10篇文章草稿在云中腐烂对任何人都没有好处。

Rust是一种由社区决定生死的语言:我能做的最好的事情就是让更多的人关注这个问题。

我要感谢所有帮助我改进本文的人,他们在发表前提供了一些反馈。解决他们指出的问题帮助我显著改善了它。