- 主题:请教个rust基本问题,Rc<RefCell<T>>有啥坏处?
swift 的速度基本跟 java/go 一档的,通常情况下 java/go 换技术的原因也都是 gc,而不是速度。我不知道 EDA 是啥,只能从更一般情况的情况谈快慢
跟 c++ 一档又带 gc 的选项倒也不是没有,比如 d 语言基本上是能跟 c++ 战的。但是呢,一般来说,大多数技术从走入大众视野,发展到成熟期,一般都在10年左右。d 走入大众视野时,node 和 go 还都不存在,所以它注定是永远小众了。一般 micro benchmark 都不考虑 gc 的影响,我很久没关注过 d 了,不知道它的 gc 目前是什么样的状态。
内存管理就这样,不手动的话,一共就3种主流技术:gc、rc、raii。都是上个世纪就存在的技术,各有各的优缺点,但没有免费的午餐。
个人认为,rc 是最差的,比手动管还糟。因为理论上,手动和 gc 是要么全管,要么全不管。而 rc 是只有一部分要手动管,还不像 gc 那样界限很清楚,只有非内存的资源要手动。我不知道有没有工具可以协助发现循环引用的问题,所以个人结论就是,极度依赖人肉。而不想手动管理的出发点就是,只要是人就会犯错,不管多高的高手,总会有翻车的时候。既比手动更依赖人肉,又有 overhead,结合了两者的缺点,所以是最糟糕的。
raii 能处理的情况非常有限,所以 rust 给出了一系列的严格规定,目的就是满足它的范畴。一旦超过了,要么 unsafe,要么 rc,都放弃了 raii 的好处。我不懂 c++,论坛看到的,c++ 几乎能完成 rust 的全部 raii 功能,但缺少最重要的编译器的严格检查。
我的建议还是 rust,但不要用 Rc,一开始写起来肯定会头疼。但从论坛反馈来看,普遍经历一段痛苦期后就习惯了。而且回头再写 c++ 也会不自觉的意识到问题,写出质量更高的代码。我个人是一直想入坑,但一直停留于想法,所以没法给出个人体会。另外,rust 的主流用法也是偏 fp 的(iterators over for loops),想正经写好本来也要一定时间的适应,所以直接 hard mode 开搞吧
【 在 beep (菜M.喵星耗子) 的大作中提到: 】
: 这里关键考虑还是性能。
: 我原来的理解是,EDA这类场景只能从c/c++/rust里面选,这三个的速度基本是可比的。go也算快的,但是典型计算场景下还是会比第一档的语言慢几倍。你所说的swift慢不到哪里去,具体有benchmark可以参考吗?
: 只要有可能,当然是希望能写有gc的语言啊,但是这不是第一档语言里都没gc嘛。。。。。。c c++ rust 各有各的恶心之处。。
: ...................
--
FROM 203.211.111.*
多谢长篇回复!
: 我不懂 c++,论坛看到的,c++ 几乎能完成 rust 的全部 raii 功能,但缺少最重要的编译器的严格检查。
多解释两句,rust的raii就是从c++抄来的,Box就是unique_ptr,Rc就是shared_ptr,几乎一样。在变量生命期方面,rust就是比c++多了一个“默认move语义”,而c++默认是copy语义。这一点需要习惯。因为默认move,所以编译期rust会给你检查是否使用了已经moved变量,提前警告。
: 我的建议还是 rust,但不要用 Rc,一开始写起来肯定会头疼。但从论坛反馈来看,普遍经历一段痛苦期后就习惯了。
rust里的Rc和普遍所说的ref count可能还有点角色上的不一样。rust里面有严格的所有权检查、读写引用冲突检查,所以其他语言里有些场合,不用rc也可以,但是在rust里,为了满足所有权传递和读写引用的要求,就必须要用rc,否则就要重新设计数据结构,甚至要对功能进行删减。
举一个例子,如果想要设计这样一个api:
struct S { f: i32 };
let mut s1 = S { f: 1 };
s1.inc().inc(); // [1] 支持先let出变量,再链式调用chain
let s2 = (S { f: 2 }).inc().inc(); // [2] 也支持先链式调用chain再初始化变量
那么怎么设计这个inc方法呢?
impl S { fn inc(mut self) -> Self { Self {f: self.f + 1} };
这样传所有权的话,只满足[2],不满足[1],因为[1]的情况下这么写 s1 就被 moved 了。
impl S { fn inc(mut& self) -> mut& Self { self.f += 1; self };
这样传引用的话,又不满足[2],因为返回的引用所指的self的生命期不够长。
这种时候貌似只能让inc返回一个Rc<S>,通过低成本的Rc::clone来满足这个api的设计要求。
另外,任何有环的数据结构,也是不可能使用RAII解决全部问题的,必须要引入Rc和Weak,人肉来操心,或者把整个数据结构看作一个整体,用arena的思路解决问题。
【 在 eGust (十年) 的大作中提到: 】
: swift 的速度基本跟 java/go 一档的,通常情况下 java/go 换技术的原因也都是 gc,而不是速度。我不知道 EDA 是啥,只能从更一般情况的情况谈快慢
: 跟 c++ 一档又带 gc 的选项倒也不是没有,比如 d 语言基本上是能跟 c++ 战的。但是呢,一般来说,大多数技术从走入大众视野,发展到成熟期,一般都在10年左右。d 走入大众视野时,node 和 go 还都不存在,所以它注定是永远小众了。一般 micro benchmark 都不考虑 gc 的
: 内存管理就这样,不手动的话,一共就3种主流技术:gc、rc、raii。都是上个世纪就存在的技术,各有各的优缺点,但没有免费的午餐。
: ...................
--
FROM 166.111.77.*
嚓,竟然m了
【 在 beep (菜M.喵星耗子) 的大作中提到: 】
: 多谢长篇回复!
: 多解释两句,rust的raii就是从c++抄来的,Box就是unique_ptr,Rc就是shared_ptr,几乎一样。在变量生命期方面,rust就是比c++多了一个“默认move语义”,而c++默认是copy语义。这一点需要习惯。因为默认move,所以编译期rust会给你检查是否使用了已经moved变量,提前警
: rust里的Rc和普遍所说的ref count可能还有点角色上的不一样。rust里面有严格的所有权检查、读写引用冲突检查,所以其他语言里有些场合,不用rc也可以,但是在rust里,为了满足所有权传递和读写引用的要求,就必须要用rc,否则就要重新设计数据结构,甚至要对功能进行删
: ...................
--
FROM 123.120.160.*
我当然知道 raii 是从 c++ 来的,不然为啥说是上个世纪就存在的技术。
循环引用的问题是,结构一旦复杂起来,人肉很难意识到哪里产生了循环引用。如果没有编译期的检查,或者即使 runtime 有可以检查的功能,但并没有调用到,那就还是只能靠人肉。
至于数据结构,按照脚本语言的经验,有 Vec 和 HashMap,可以避免很大一部分自定义的复杂结构。而且一般意义上的数据结构,除非太冷门一般都会有现成的库可以用。如果能避免使用 Rc 的话,还是尽量 raii,这样才能最大化利用编译器带来的好处。如果无脑直接抄 poc 项目的代码,上来就优先考虑 Rc,那真的没啥必要非得 rust。用 c++ 不也一样可以直接翻译代码么,都是从零开始还能麻烦到哪去
【 在 beep (菜M.喵星耗子) 的大作中提到: 】
: 多谢长篇回复!
: 多解释两句,rust的raii就是从c++抄来的,Box就是unique_ptr,Rc就是shared_ptr,几乎一样。在变量生命期方面,rust就是比c++多了一个“默认move语义”,而c++默认是copy语义。这一点需要习惯。因为默认move,所以编译期rust会给你检查是否使用了已经moved变量,提前警
: rust里的Rc和普遍所说的ref count可能还有点角色上的不一样。rust里面有严格的所有权检查、读写引用冲突检查,所以其他语言里有些场合,不用rc也可以,但是在rust里,为了满足所有权传递和读写引用的要求,就必须要用rc,否则就要重新设计数据结构,甚至要对功能进行删
: ...................
--
FROM 203.211.111.*
rust初学者友好,
c++真的是不知从哪开始
【 在 eGust 的大作中提到: 】
: 我当然知道 raii 是从 c++ 来的,不然为啥说是上个世纪就存在的技术。
:
: 循环引用的问题是,结构一旦复杂起来,人肉很难意识到哪里产生了循环引用。如果没有编译期的检查,或者即使 runtime 有可以检查的功能,但并没有调用到,那就还是只能靠人肉。
: ....................
--
FROM 124.160.154.*
不明觉厉
感觉本帖子链里唯一有真东西的一个
不过举的例子有点小问题应该没让编译器把关吧:
1、
impl S { fn inc(mut self) -> Self { Self {f: self.f + 1} };
-->
impl S { fn inc(self) -> Self { Self {f: self.f + 1} } }
加个 mut 没用
2、
impl S { fn inc(mut& self) -> mut& Self { self.f += 1; self };
-->
impl S { fn inc(&mut self) -> &mut Self { self.f += 1; self } }
mut& 是写混乱了
符合条件一种写法是:
impl S { fn inc(&self) -> Rc<S> { Rc::new(Self { f: self.f + 1 }) } }
【 在 beep (菜M.喵星耗子) 的大作中提到: 】
: 标 题: Re: 请教个rust基本问题,Rc<RefCell<T>>有啥坏处?
: 发信站: 水木社区 (Fri Dec 10 16:46:14 2021), 站内
:
: 多谢长篇回复!
:
: : 我不懂 c++,论坛看到的,c++ 几乎能完成 rust 的全部 raii 功能,但缺少最重要的编译器的严格检查。
:
: 多解释两句,rust的raii就是从c++抄来的,Box就是unique_ptr,Rc就是shared_ptr,几乎一样。在变量生命期方面,rust就是比c++多了一个“默认move语义”,而c++默认是copy语义。这一点需要习惯。因为默认move,所以编译期rust会给你检查是否使用了已经moved变量,提前警告。
:
: : 我的建议还是 rust,但不要用 Rc,一开始写起来肯定会头疼。但从论坛反馈来看,普遍经历一段痛苦期后就习惯了。
: rust里的Rc和普遍所说的ref count可能还有点角色上的不一样。rust里面有严格的所有权检查、读写引用冲突检查,所以其他语言里有些场合,不用rc也可以,但是在rust里,为了满足所有权传递和读写引用的要求,就必须要用rc,否则就要重新设计数据结构,甚至要对功能进行删减。
:
: 举一个例子,如果想要设计这样一个api:
:
: struct S { f: i32 };
: let mut s1 = S { f: 1 };
: s1.inc().inc(); // [1] 支持先let出变量,再链式调用chain
: let s2 = (S { f: 2 }).inc().inc(); // [2] 也支持先链式调用chain再初始化变量
:
: 那么怎么设计这个inc方法呢?
:
: impl S { fn inc(mut self) -> Self { Self {f: self.f + 1} };
: 这样传所有权的话,只满足[2],不满足[1],因为[1]的情况下这么写 s1 就被 moved 了。
:
: impl S { fn inc(mut& self) -> mut& Self { self.f += 1; self };
: 这样传引用的话,又不满足[2],因为返回的引用所指的self的生命期不够长。
:
: 这种时候貌似只能让inc返回一个Rc<S>,通过低成本的Rc::clone来满足这个api的设计要求。
:
: 另外,任何有环的数据结构,也是不可能使用RAII解决全部问题的,必须要引入Rc和Weak,人肉来操心,或者把整个数据结构看作一个整体,用arena的思路解决问题。
:
:
: 【 在 eGust (十年) 的大作中提到: 】
: : swift 的速度基本跟 java/go 一档的,通常情况下 java/go 换技术的原因也都是 gc,而不是速度。我不知道 EDA 是啥,只能从更一般情况的情况谈快慢
: : 跟 c++ 一档又带 gc 的选项倒也不是没有,比如 d 语言基本上是能跟 c++ 战的。但是呢,一般来说,大多数技术从走入大众视野,发展到成熟期,一般都在10年左右。d 走入大众视野时,node 和 go 还都不存在,所以它注定是永远小众了。一般 micro benchmark 都不考虑 gc 的
: : 内存管理就这样,不手动的话,一共就3种主流技术:gc、rc、raii。都是上个世纪就存在的技术,各有各的优缺点,但没有免费的午餐。
: : ...................
:
: --
:
: ※ 来源:·水木社区 mysmth.net·[FROM: 166.111.77.*]
--
修改:No1 FROM 221.216.249.*
FROM 106.121.187.*
一个C++里简单的级联操作,需要这么实现么?
再怎么move,效率上还是有影响的
【 在 No1 () No1 () 的大作中提到: 】
: 不明觉厉
: 感觉本帖子链里唯一有真东西的一个
: 不过举的例子有点小问题应该没让编译器把关吧:
: ...................
--
FROM 111.197.236.182
rustc编译器在它认为应该回收的一切东西foo后立即自动加上 drop(foo),
c++ 一般手动delete,c++中忘了delete也就忘了编译器不会理你,
而rust中要这foo还想活下来就得想办法明确告诉它不要立即加上drop(foo)这句代码。
除了编译更慢更费劲,理论上运行时不会慢,不就多了些本就应该有的drop代码吗
【 在 jjfz (每天两壶茶) 的大作中提到: 】
: 标 题: Re: 请教个rust基本问题,Rc<RefCell<T>>有啥坏处?
: 发信站: 水木社区 (Sat Dec 11 18:10:44 2021), 站内
:
: 一个C++里简单的级联操作,需要这么实现么?
: 再怎么move,效率上还是有影响的
:
:
: 【 在 No1 () No1 () 的大作中提到: 】
: : 不明觉厉
: : 感觉本帖子链里唯一有真东西的一个
: : 不过举的例子有点小问题应该没让编译器把关吧:
: : ...................
:
: --
: 我能忍受寂寞,只是不喜欢寂寞
: 我不喜欢寂寞,只是常处于寂寞
: -清华大学电子系eda实验室驻SMTH拒绝吃喝办事处处长JJFZ
:
:
: ※ 来源:·水木社区 mysmth.net·[FROM: 111.197.236.182]
--
修改:No1 FROM 221.216.249.*
FROM 221.216.249.*
多谢纠正,边开会边手敲的,没过脑子。。
【 在 No1 的大作中提到: 】
: 不明觉厉
: 感觉本帖子链里唯一有真东西的一个
: 不过举的例子有点小问题应该没让编译器把关吧:
: ...................
--
FROM 223.104.39.*
【 在 eGust (十年) 的大作中提到: 】
: 标 题: Re: 请教个rust基本问题,Rc<RefCell<T>>有啥坏处?
: 发信站: 水木社区 (Fri Dec 10 20:47:19 2021), 站内
: 至于数据结构,按照脚本语言的经验,有 Vec 和 HashMap,可以避免很大一部分自定义的复杂结构。而且一般意义上的数据结构,除非太冷门一般都会有现成的库可以用。如果能避免使用 Rc 的话,还是尽量 raii,这样才能最大化利用编译器带来的好处。如果无脑直接抄 poc 项目的代码,上来就优先考虑 Rc,那真的没啥必要非得 rust。用 c++ 不也一样可以直接翻译代码么,都是从零开始还能麻烦到哪去
c++ 和 rust 相比,差的可不仅仅是这一点。我们先是尝试了大半个月 c++,发现:
1. 没有主流包管理
2. cmake 写死人
3. pybinding 对接个 python,写起来也好烦好烦
4. raii 只是“建议”不是强制,内存安全还是要依靠人肉 code review
5. 连个像样的 string 都没有
6. 。。。。。
相反,尝试 rust 之后,以上问题都非常顺畅地解决了。pyo3 随便一跑就跑通了,比
pybinding 用起来不知道简单多少。
目前的结论是,如果要写无 gc 的高性能的东西,肯定不会去选 c/c++, 肯定是 rust。如果无 gc 的语言写起来太劳神,那也只能牺牲一点点性能,考虑 go swift 这种。肯定是不会去碰 c++ 的。。。
:
: 【 在 beep (菜M.喵星耗子) 的大作中提到: 】
: : 多谢长篇回复!
: : 多解释两句,rust的raii就是从c++抄来的,Box就是unique_ptr,Rc就是shared_ptr,几乎一样。在变量生命期方面,rust就是比c++多了一个“默认move语义”,而c++默认是copy语义。这一点需要习惯。因为默认move,所以编译期rust会给你检查是否使用了已经moved变量,提前警
: : rust里的Rc和普遍所说的ref count可能还有点角色上的不一样。rust里面有严格的所有权检查、读写引用冲突检查,所以其他语言里有些场合,不用rc也可以,但是在rust里,为了满足所有权传递和读写引用的要求,就必须要用rc,否则就要重新设计数据结构,甚至要对功能进行删
: : ...................
:
: --
:
: ※ 来源:·水木社区 mysmth.net·[FROM: 203.211.111.*]
--
FROM 183.173.173.*