什么时候可以使用`std::process::exit`?

7
std::process::exit的文档中写道:

如果需要进行干净的关闭,建议仅在已知没有剩余析构函数需要运行的时候调用此函数。

也许由于我缺乏系统编程背景,我不知道在特定点是否还有析构函数需要运行,也不知道是否需要关心。唯一想到的是对文件(或其他东西)进行挂起的写操作,在这种情况下将事情留在一个干净的状态似乎是个好主意。
还有什么需要注意的吗?我怀疑在更大、更复杂的程序中使用它可能不明智,但对于小工具来说似乎很方便。

如果您不处理任何敏感数据或连接,那么您不必担心它。您可以在https://dev59.com/MWEi5IYBdhLWcg3wJpgl#21570851中查看备注。 - ljedrz
1个回答

9

简短回答:

  • 几乎所有情况下都应该使用panic!()
  • 仅当...
    • ...在main()函数中
    • ...手动处理堆栈并进行解绑操作(可能不需要...)
  • 有时候可以忽略退出程序时的析构函数,但是需要小心!

稍长一些的解释

[...] 对于小工具似乎很方便。

如果您想因无法恢复的错误退出程序,则建议使用panic!()。这将解绑堆栈(运行所有析构函数)并带有附加信息退出程序(包括您可以指定的消息字符串)。

[...] 在某个特定的点上是否还有析构函数需要运行,以及我是否需要关心。

如果在当前堆栈帧或任何上层堆栈帧中存在实现Drop的局部变量,则在该点上仍然存在析构函数。 堆栈框架是保存函数调用的所有局部变量的堆栈内存区域(口语上说)。 函数退出后,所有丢弃的局部变量都会被销毁,这包括调用实现Drop的所有变量的析构函数。

一些析构函数需要运行的概率随着堆栈深度(“从main()到当前堆栈帧之间调用了多少个函数”)增加而增加。因此,除非您位于main()函数中,否则很难对此进行推理。


什么时候类型会实现Drop?当直接忽略它们是错误的时候。考虑一个i32:我们退出函数时可以将其留在堆栈内存中,因为它没有任何负面影响(暂且不考虑数据安全的特殊情况)。但是,有许多类型确实需要实现Drop。以下是几个分类:

  • 分配堆内存:Box<T>Vec<T>HashMap<T>
  • 持有操作系统资源:FileSocket
  • 返回句柄:RefMutexGuard
  • ...
不运行这些析构函数会产生不同的影响。第一组可能是最无害的:我们将泄漏内存。但当您退出程序时,操作系统将清理所有这些内存。几乎相同的情况也适用于操作系统资源:文件描述符通常在程序退出时由操作系统删除。
但是有更多原因要运行析构函数。需要考虑的重要点是:您经常不知道为什么某些类型实现了“Drop”,但这些类型确实依赖它。忽略它可能会发生某些不希望的事情。通常您不会真正注意到,但有时它可能导致严重问题。

1
一个非常详尽和启发性的回答!我在解析文件时,如果发现语法错误,就会在不同的地方使用panic!(),但我想避免“Thread 'main' panicked...”这样的噪音,只呈现有关我发现的问题的信息。如果我的堆栈帧中只有像字符串、字符、整数、我自己定义的简单枚举和结构体以及我仅从中读取的文件这样的简单数据类型,我几乎敢把清理工作留给操作系统。 - Thomas W
3
在程序中,如果遇到非程序逻辑错误,可以使用Result将其向上抛出到main函数,在main函数的结尾处使用std::process::exit控制程序返回操作系统的退出码。对于程序逻辑错误,可以使用panic!。请注意,在翻译过程中,不应该改变原文的意思。 - Shepmaster
1
@ThomasW,我认为你应该阅读Rust书中的“错误处理”章节。此外,String类型有时感觉很“简单”,但它非常复杂(UTF8 / Unicode),并且还实现了Drop,因为字符串缓冲区位于堆上。 - Lukas Kalbertodt
@LukasKalbertodt 我理解字符串的问题,但是让操作系统清理它们的内存是否存在任何危险?我知道 Result 是处理这种错误最干净的方式。(除非懒惰发作,因为那个冒泡不会自己发生,需要实现。) - Thomas W
1
@ThomasW 不,操作系统可以轻松清理堆分配的内存。我想引起注意的是,除了操作系统可能无法处理的所有其他析构函数原因。至于懒惰:除非已经完成,否则请阅读该章节。Rust错误处理令人惊讶地优雅且通常很短。try!()和其他技巧使这成为可能。 - Lukas Kalbertodt
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接