Silva: How to use the new counted_by attribute in C (and Linux)
There are a number of requirements to properly use the counted_by attribute. One crucial requirement is that the counter must be initialized before the first reference to the flexible-array member. Another requirement is that the array must always contain at least as many elements as indicated by the counter.
See also: this article from 2023.
Posted Jul 17, 2024 14:54 UTC (Wed)
by python (guest, #171317)
[Link] (12 responses)
Posted Jul 18, 2024 0:02 UTC (Thu)
by wahern (subscriber, #37304)
[Link]
Posted Jul 18, 2024 2:48 UTC (Thu)
by milesrout (subscriber, #126894)
[Link] (9 responses)
This addition looks to me more like a natural extension of the work done around _FORTIFY_SOURCE etc. I believe these significantly predate Rust.
Posted Jul 18, 2024 6:39 UTC (Thu)
by pbonzini (subscriber, #60935)
[Link] (8 responses)
Posted Jul 19, 2024 6:00 UTC (Fri)
by bluss (guest, #47454)
[Link] (7 responses)
The `unsafe` keyword is used to say that "I've ensured myself that Rust's expectations and rules are followed", it's not used for "anything goes" unfortunately.
Posted Jul 19, 2024 6:06 UTC (Fri)
by mb (subscriber, #50428)
[Link] (6 responses)
But I think flexible array members are not really needed in Rust. Generics should cover many use cases.
Posted Jul 19, 2024 17:56 UTC (Fri)
by bluss (guest, #47454)
[Link] (5 responses)
1. Rust struct field order is impdef by default. Add #[repr(C)] to actually ensure array is the last field member (that would be necessary for this to work).
Posted Jul 19, 2024 19:47 UTC (Fri)
by mb (subscriber, #50428)
[Link] (4 responses)
Posted Jul 20, 2024 12:13 UTC (Sat)
by bluss (guest, #47454)
[Link] (3 responses)
Plain flexible length allocations can use `Box<[T]>`. For the true flexible array member use case, some non-native library solution is needed, and there seems to be crates available that implement this. I think I would look at https://docs.rs/thin-dst/ for this, but I don't know it well.
Posted Jul 22, 2024 17:54 UTC (Mon)
by ms-tg (subscriber, #89231)
[Link] (2 responses)
If so, is there a way to return a `Box<[T]>` in a general way to use in Rust code that is handed a C flexible array, that is close to zero cost?
(these are genuine questions, I don't know the answer)
Posted Jul 22, 2024 21:29 UTC (Mon)
by atnot (guest, #124910)
[Link] (1 responses)
I'll start with the no: Box is just a heap allocation, and [T] is just a contiguous set of elements. So a Box<[T]> compiles down to a pointer to a variable sized heap allocation, which is different.
The reason it is done this way is that [T] is one of the few types in Rust that are "Unsized", that is, do not have a size known at compile time. The things you can do with unsized types in safe Rust are extremely limited. They can only exist behind a pointer, you can't create them, put them on the stack, or any of the other things you can do in C. Really the only thing you can do is put them in a Box to get a nice, constant size type again, hence Box<[T]>. You will never see a raw [T] writing normal Rust because of how useless it is.
So no, it's not a direct replacement, unless your flexible array is just {size, buffer[]}. Anything else doesn't really translate to safe Rust. However, this does represent the far majority of cases where flexible array members are used in C in practice. There are a few exceptions, but it's generally not a clear performance win and they often use funky bit stashing tricks that counted_by wouldn't understand either anyway. So it's close enough in practice.
However, while Box<[T]> might only be equivalent in some cases, [T] does exist and can actually be the last member of a struct to create a dynamically sized struct, exactly like in C. In fact to my knowledge it was added for C ffi. It won't be pretty and it will be highly unsafe. But you can totally do it, if you wish to.
Why not in safe Rust? Probably could be done, I just don't think anyone cares enough.
Posted Jul 24, 2024 22:03 UTC (Wed)
by rodrigorc (guest, #89475)
[Link]
In particular the `ptr_metadata` feature is quite handy. This sample code runs in nightly Miri without warnings:
Posted Jul 25, 2024 2:31 UTC (Thu)
by mrugiero (guest, #153040)
[Link]
Posted Jul 18, 2024 3:08 UTC (Thu)
by songmaster (subscriber, #1748)
[Link] (1 responses)
In Jon’s 2023 article he showed a different attribute name and the count member name in quotes inside the attribute: The online GCC and Clang documentation only mention counted_by and no sign of the quotes, so did the compilers agree on a better name while implementing this?
Posted Jul 18, 2024 4:54 UTC (Thu)
by Tarnyko (guest, #90061)
[Link]
#if __has_attribute(__counted_by__)
Posted Jul 18, 2024 15:05 UTC (Thu)
by mirabilos (subscriber, #84359)
[Link]
Culture
Culture
What influenced this addition?
Sort of, you can use a zero-sized array and access it with runtime bounds checks
What influenced this addition?
use std::ops::Index;
use std::slice;
struct S {
n: usize,
array: [u32; 0]
}
impl Index<usize> for S {
type Output = u32;
fn index(&self, index: usize) -> &u32 {
unsafe {
&slice::from_raw_parts(self.array.as_ptr(), self.n)[index]
}
}
}
but there's no equivalent of counted_by. I agree that it's more similar to _FORTIFY_SOURCE.
What influenced this addition?
What influenced this addition?
I think the example will panic on an out of bounds access.
What influenced this addition?
2. Pointer provenance rules/stacked borrow rules - any pointer or reference derived from S.array is valid for exactly 0 elements, so we can't actually write code that indexes (computes a pointer) based on this field - not even using raw pointers. Miri will point out this error and say it's Undefined Behaviour. (All of creating the pointer - llvm GetElementPtr's inbounds rule, reading from the pointer, writing to the pointer).
What influenced this addition?
What influenced this addition?
What influenced this addition?
What influenced this addition?
There are a few new features, that hopefully will be stabilized soon, that will make the "unsized struct with unsized last field" trick quite workable.
What influenced this addition?
#![feature(ptr_metadata)]
use std::ops::Index;
use std::slice;
#[repr(C)]
struct S {
n: usize,
array: [u32]
}
impl Index<usize> for S {
type Output = u32;
fn index(&self, index: usize) -> &u32 {
unsafe {
&slice::from_raw_parts(self.array.as_ptr(), self.n)[index]
}
}
}
fn main() {
unsafe {
// Fake C code:
let layout = std::alloc::Layout::from_size_align(100, 4).unwrap();
let ptr = std::alloc::alloc_zeroed(layout);
ptr.cast::<usize>().write(10);
// Use the flex array:
// First with size=0, a distinct Sized type for the header might look nicer
let n = (*std::ptr::from_raw_parts::<S>(ptr, 0)).n;
let s = &*std::ptr::from_raw_parts::<S>(ptr, n);
for i in 0 .. s.n {
println!("{} {}", i, s[i]);
}
// More fake C code
std::alloc::dealloc(ptr, layout);
}
}
Culture
Should “count” be quoted?
__attribute__((element_count(“count”)))
Should “count” be quoted?
# define __counted_by(member) __attribute__((__counted_by__(member)))
#else
# define __counted_by(member)
#endif
Strings
