-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Support closures as array initializer #2092
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Your implementation is unsound for general closures, because the closure might panic, leaving the array in an undefined state. This is a problem, because I recommend to give this crate a try: https://github.com/Manishearth/array-init |
How would it be different from having the following code? fn fun(x: usize, y: usize) -> Codebook<u8> {
if y != 0 { panic!() };
Codebook {x: x as u8, y: y as u8}
}
...
let c = [fun(1, 0), fun(2, 1), fun(3, 0)];
println!("{:?}", c); https://play.rust-lang.org/?gist=d85cfc9f6d0d3e2e4cf6ace565d226df&version=stable functions used to initialize an array could panic and leave it partially initialized already. The whole proposal started because if you have to compile large (think few k elements) tables you'd like to do that in the most succinct way. |
Nope, if one of the array element initializers panics then only the successfully constructed elements are dropped. In your fill_thing_array example all of the uninitialized elements are dropped as well. This would go wrong if T=String for example, String's drop implementation would segfault (if you are lucky). Here is an example that demonstrates the difference. #[derive(Debug)]
struct NoisyDrop(u8);
impl Drop for NoisyDrop {
fn drop(&mut self) {
println!("dropping {:?}", self);
}
}
#[derive(Debug)]
struct Codebook<T> {
x: T,
y: T,
}
fn fun(x: usize, y: usize) -> Codebook<NoisyDrop> {
if y != 0 { panic!() };
println!("constructing codebook with x={} y={}", x, y);
Codebook { x: NoisyDrop(x as u8), y: NoisyDrop(y as u8) }
}
fn main() {
// fun(2, 1) panics so only fun(1, 0) is dropped
let c = [fun(1, 0), fun(2, 1), fun(3, 0)];
println!("{:?}", c);
} As you can see, fun(1, 0) is successfully constructed and then dropped after the panic.
In the other approach: use std::mem;
#[derive(Debug)]
struct NoisyDrop(u8);
impl Drop for NoisyDrop {
fn drop(&mut self) {
println!("dropping {:?}", self);
}
}
#[derive(Debug)]
struct Codebook<T> {
x: T,
y: T,
}
fn fill_thing_array(a: &mut [[Codebook<NoisyDrop>; 4]; 4]) {
for (i, line) in a.iter_mut().enumerate() {
for (j, el) in line.iter_mut().enumerate() {
if j != 0 { panic!() };
println!("constructing codebook with x={} y={}", i, j);
*el = Codebook { x: NoisyDrop(i as u8), y: NoisyDrop(j as u8) };
}
}
}
fn main() {
let mut a = unsafe { mem::uninitialized() };
fill_thing_array(&mut a);
} The output is:
Only one element was constructed but a bunch of uninitialized data was dropped.
|
Some previous discussion on this topic: #1915 (comment) (That RFC was closed, choosing to wait for const generics.) |
That seems to make even a better case for a compact syntax and have it desugar to the unrolled initialization. |
I somehow missed a reference to array-init in this thread, and for some reason created my own implementation in style of This one doesn't have limitation to number of elements, but it must be a constant integer (not even constant mathematical expression) and cannot run macro inside a macro (for some reason procedural macros don't like this) (actually cannot run macro inside a macro seems like dtolnay/proc-macro-hack#4) |
I don't see why we need special syntax when it can be implemented as a
library.
…On Tue, Aug 1, 2017, 19:18 Luca Barbato ***@***.***> wrote:
That seems to make even a better case for a compact syntax and have it
desugar to the unrolled initialization.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#2092 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AACCtPnwN94PHrgs7iigSnRturJ4ldhgks5sT131gaJpZM4OpmrS>
.
|
I think there should just be an |
Or I guess |
Something like that could be a possible implementation of #![feature(untagged_unions)]
use std::iter::FromIterator;
use std::mem;
use std::ptr;
// cannot define std implementations for builtin types, so
// i define a wrapper to do so
struct Wrapper<T>(T);
#[allow(unions_with_drop_fields)]
union NoDrop<T> {
storage: T,
}
struct ArrayVec<F, T>
where
F: Fn(&mut T, usize),
{
drop_elems: F,
array: NoDrop<T>,
length: usize,
}
impl<F, T> Drop for ArrayVec<F, T>
where
F: Fn(&mut T, usize),
{
fn drop(&mut self) {
unsafe {
(self.drop_elems)(&mut self.array.storage, self.length);
}
}
}
macro_rules! impl_from_iterator {
($($n:tt)*) => { $(
impl<T> FromIterator<T> for Wrapper<Option<[T; $n]>> {
fn from_iter<I>(iter: I) -> Self
where
I: IntoIterator<Item = T>,
{
unsafe {
// Exists so that element that overflows an array is destructed last
let _last;
let mut array_vec = ArrayVec {
drop_elems: |slice: &mut [T; $n], length| {
for item in slice.iter_mut().take(length) {
ptr::drop_in_place(item);
}
},
array: NoDrop { storage: mem::uninitialized() },
length: 0,
};
for elem in iter {
if array_vec.length == $n {
_last = elem;
return Wrapper(None);
}
ptr::write(&mut array_vec.array.storage[array_vec.length], elem);
array_vec.length += 1;
}
if array_vec.length != $n {
return Wrapper(None);
}
let storage = ptr::read(&array_vec.array.storage);
mem::forget(array_vec);
Wrapper(Some(storage))
}
}
}
)* };
}
impl_from_iterator!(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20);
#[derive(Debug)]
struct ReportDrops(i32);
impl Drop for ReportDrops {
fn drop(&mut self) {
println!("{}", self.0);
}
}
fn main() {
{
let Wrapper(arr): Wrapper<Option<[ReportDrops; 4]>> = (0..4).map(ReportDrops).collect();
println!("{:?}", arr);
}
println!("---");
{
let Wrapper(arr): Wrapper<Option<[ReportDrops; 4]>> = (0..3).map(ReportDrops).collect();
println!("{:?}", arr);
}
println!("---");
{
let Wrapper(arr): Wrapper<Option<[ReportDrops; 4]>> = (0..42).map(ReportDrops).collect();
println!("{:?}", arr);
}
} Output:
|
@vks array initialization in certain fields of programming is quite common and would be nice to have an ergonomic way to do that that doesn't require lots of lines of code. |
@lu-zero I agree that it would be nice to have better support for arrays in the standard library. I'm however not convinced that we need to add new syntax. The current workarounds (each with individual shortcomings) are:
I think it is ergonomic and does not require lots of code: let arr: [u32; 50] = array_init::array_init(|i| (i*i) as u32); |
array_init currently does not work for large arrays and I'm not 100% sure it would work with multi-dimensional arrays. Probably I could try to make a procedural macro and see how it behaves. |
Currently the way to initialize an array is a bit unwieldy:
Would be nicer to provide a syntax to have something along the lines of
The text was updated successfully, but these errors were encountered: