英文:
rust “array struct” for constant length arrays
问题
For 3D-modelling based on triangles, I use a C struct like
pub struct Point {
pub x: f32,
pub y: f32,
pub z: f32,
};
and tuple struct like
pub struct Triangle (
pub Point,
pub Point,
pub Point,
);
Algorithms based on triangles often have to iterate all points of a triangle. I can do this with
for point in [ triangle.0, triangle.1, triangle.2 ].iter() { … }
but I imagine that the conversion from Triangle to array will always cost some runtime, and I do not think this is an elegant idiom anyway. I would prefer if I could use a kind of type that I might call "array structs" of the form
pub struct Triangle [pub Point; 3];
which would itself implement iter
and thus allow
for point in triangle.iter() { … }
I know that such "array struct" syntax is not available. So what would be a best practice idiom for the situation where a named type should essentially hold an array of one basic element type where the length of the array is known and fixed at compile time?
英文:
For 3D-modelling based on triangles, I use a C struct like
pub struct Point {
pub x: f32,
pub y: f32,
pub z: f32,
};
and tuple struct like
pub struct Triangle (
pub Point,
pub Point,
pub Point,
);
Algorithms based on triangles often have to iterate all points of a triangle. I can do this with
for point in [ triangle.0, triangle.1, triangle.2 ].iter() { … }
but I imagine that the conversion from Triangle to array will always cost some runtime, and I do not think this is an elegant idiom anyway. I would prefer if I could use a kind of type that I might call “array structs” of the form
pub struct Triangle [pub Point; 3];
which would itself implement iter
and thus allow
for point in triangle.iter() { … }
I know that such “array struct” syntax is not available. So what would be a best practice idiom for the situation where a named type should essentially hold an array of one basic element type where the length of the array is known and fixed at compile time?
答案1
得分: 1
如果你有小数组,比如少于10-12个元素,这不会引起太多问题,因为编译器会展开循环并通过直接从三角形中复制来删除数组。
请参见 godbolt。
例如,这段代码:
pub fn update_triangles(triangle: &Triangle, process: fn(Point)){
for point in [ triangle.0, triangle.1, triangle.2 ].iter() {
process(*point);
}
}
会转化为以下汇编代码:
example::update_triangles:
push r15
push r14
push rbx
sub rsp, 16
mov rbx, rsi
mov r14, rdi
mov eax, dword ptr [rdi + 8]
mov dword ptr [rsp + 8], eax
mov rax, qword ptr [rdi]
mov qword ptr [rsp], rax
mov r15, rsp
mov rdi, r15
call rsi
mov eax, dword ptr [r14 + 20]
mov dword ptr [rsp + 8], eax
mov rax, qword ptr [r14 + 12]
mov qword ptr [rsp], rax
mov rdi, r15
call rbx
mov eax, dword ptr [r14 + 32]
mov dword ptr [rsp + 8], eax
mov rax, qword ptr [r14 + 24]
mov qword ptr [rsp], rax
mov rdi, r15
call rbx
add rsp, 16
pop rbx
pop r14
pop r15
ret
然而,对于更大的类型和数组,情况可能不成立。
另一种选择是为你的类型实现迭代器:
impl Triangle {
fn points(&'_ self)->impl Iterator<Item=Point> + '_' {
(0..3).map(move|i|
match i {
0 => self.0,
1 => self.1,
2 => self.2,
_ => unreachable!(),
}
)
}
}
pub fn update_triangles(triangle: &Triangle, process: fn(Point)){
for point in triangle.points() {
process(point);
}
}
在这种情况下,它生成了没有额外内存使用的展开循环:godbolt。
不过,你需要小心,因为如果编译器在优化时失败,与使用数组相比,你会得到更多的分支,这可能导致代码执行较慢。
1: https://godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(filename:'1',fontScale:14,fontUsePx:'0',j:1,lang:rust,selection:(endColumn:18,endLineNumber:15,positionColumn:18,positionLineNumber:15,selectionStartColumn:18,selectionStartLineNumber:15,startColumn:18,startLineNumber:15),source:'#[derive(Clone,Copy)]\npub struct Point{\n pub x:f32,pub y:f32,pub z:f32\n}\n\n#[derive(Clone,Copy)]\npub struct Triangle(\n pub Point,\n pub Point,\n pub Point,\n);\n\npub fn update_triangles(triangle: &Triangle, process: fn(Point)){\n for point in [ triangle.0, triangle.1, triangle.2 ].iter() { \n process(*point);\n }\n}\n\npub fn update_triangles_inline(\n triangle: &Triangle, process: fn(Point)\n){\n process(triangle.0);\n process(triangle.1);\n process(triangle.2);\n}' ),l:'5',n:'0',o:'Rust source #1',t:'0')),k:42.18941996092467,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:r1700,deviceViewOpen:'1',filters:(b:'0',binary:'1',binaryObject:'1',commentOnly:'0',debugCalls:'1',demangle:'0',directives:'0',execute:'1',intel:'0',libraryCode:'0',trim:'1'),flagsViewOpen:'1',fontScale:14,fontUsePx:'0',j:1,lang:rust,libs:!(),options:'-Copt-level=3',overrides:!(),selection:(endColumn:1,endLineNumber:1,positionColumn:1,positionLineNumber:1,selectionStartColumn:1,selectionStartLineNumber:1,startColumn:1,startLineNumber:1),source:1),l:'5',n:'0',o:'+rustc+1.70.0+(Editor+#1)',t:'0')),k:32.883854902479285,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:output,i:(compilerName:'clang+16.0.0',editorid:1,fontScale:14,fontUsePx:'0',j:1,wrap:'1'),l:'5',n:'0',o:'Output+of+rustc+1.70.0+(Compiler+#1)',t:'0')),k:24.926725136596044,l:'4',n:'0',o:'',s:0,t:'0')),l:'2',n:'0',o:'',t:'0')),version:4
2: https://godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(filename:'1',fontScale:14,fontUsePx:'0',j:1,lang:rust,selection:(endColumn:2,endLineNumber:30,positionColumn:1,positionLineNumber:13,selectionStartColumn:2,selectionStartLineNumber:30,startColumn:1,startLineNumber:13),source:'#[derive(Clone,Copy)]\npub struct Point{\n pub x:f32,pub y:f32,pub z:f32\n}\n\n#[derive(Clone,Copy)]\npub struct Triangle(\n pub Point,\n pub Point,\n pub Point,\n);\n\nimpl Triangle {\n fn points(&'_ self)->impl Iterator<Item=Point> + '_' {\n (0..3).map(move|i|\n match i {\n 0 => self.0,\n 1 => self.1,\n 2 => self.2,\n _ => unreachable!(),\n }\n )\n }\n}\n\npub fn update_triangles(triangle
英文:
If you have tiny arrays like less than 10-12 elements, it wouldn't cause a lot of problems because compiler unrolls loop and removes array as well by using direct copies from triangle.
See godbolt.
For example, this code:
pub fn update_triangles(triangle: &Triangle, process: fn(Point)){
for point in [ triangle.0, triangle.1, triangle.2 ].iter() {
process(*point);
}
}
converts to this asm:
example::update_triangles:
push r15
push r14
push rbx
sub rsp, 16
mov rbx, rsi
mov r14, rdi
mov eax, dword ptr [rdi + 8]
mov dword ptr [rsp + 8], eax
mov rax, qword ptr [rdi]
mov qword ptr [rsp], rax
mov r15, rsp
mov rdi, r15
call rsi
mov eax, dword ptr [r14 + 20]
mov dword ptr [rsp + 8], eax
mov rax, qword ptr [r14 + 12]
mov qword ptr [rsp], rax
mov rdi, r15
call rbx
mov eax, dword ptr [r14 + 32]
mov dword ptr [rsp + 8], eax
mov rax, qword ptr [r14 + 24]
mov qword ptr [rsp], rax
mov rdi, r15
call rbx
add rsp, 16
pop rbx
pop r14
pop r15
ret
This may be not true for larger types and arrays though.
Another option is to implement iterator for your type:
impl Triangle {
fn points(&'_ self)->impl Iterator<Item=Point> + '_ {
(0..3).map(move|i|
match i {
0 => self.0,
1 => self.1,
2 => self.2,
_ => unreachable!(),
}
)
}
}
pub fn update_triangles(triangle: &Triangle, process: fn(Point)){
for point in triangle.points() {
process(point);
}
}
In this case, it generates unrolled loop without extra memory usage too: godbolt.
You need to be careful with that though because if compiler would fail with optimization, you would get more branches than when using array which may lead to slower code execution.
答案2
得分: 0
我可以使用命名类型,就像这样:
type Triangle = [ Point; 3 ];
然后我可以循环遍历 triangle.iter()
,甚至可以更简单地不使用显式的 iter
:
for point in triangle { … }
(在问题中的代码中也可以省略 iter()
。)
英文:
Instead of a struct
I have found out I can use a named type like this:
type Triangle = [ Point; 3 ];
Then I can loop over triangle.iter()
, and even simpler without explicit iter
:
for point in triangle { … }
(Dropping iter()
would have been possible in the code in the question, too.)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论