Asynchronous synchronous code


#1

Hi everyone!

I’m a Rust noob, and I may have just stumbled into something easy to fix that, as a noob, I just can’t fix so far.

Well, I’ve got a function mapping an array, nothing weird. For debugging purposes it prints the array before returning it.
You can find it here: https://github.com/yobicash/libyobicash/blob/testing/src/models/outpoint/mod.rs#L101

Then there is this test, where the printed, existing array, get returned empty, and printed empty. What’s even stranger, is that it seems that the various prints get executed disorderly.
You can find the code here: https://github.com/yobicash/libyobicash/blob/testing/tests/models/tx/mod.rs#L471

It’s rustup v1.5.0, rustc v1.18.0, cargo v0.19.0. Just cargo test – --nocapture.

So, noob question: does Rust have some sort of asynchronicity in synchronous code?


#2

Oh, well, one more info: it’s stable.

I really can’t wrap my head around this problem, and I don’t get if it’s very stupid or very scary or that I’ve just misunderstood something on tests execution, etc.


#3

Regarding the order of the prints, cargo test runs tests concurrently, order of execution is not deterministic.


#4

I see two calls to outpoints.to_inputs()
Without your printed output or running the code no idea what you describe as disorderly.


#5

Regarding the empty inputs, obviously that means your inputs vec in to_inputs() is empty. Given that to_inputs() returns Ok then that means you didn’t push anything on it, which means you that your iterator’s first call to next() should have returned None and stopped there. So either your vec of outpoints is empty (which doesn’t seem to be the case) or calling self.check() on your first outpoint returned an Err which you turned into a None in your Iterator impl. So it stopped there.


#6

It’s important to understand that iterators stop at the first None encountered, but that doesn’t always mean there isn’t any other element left afterwards.

From the stdlib doc:

fn next(&mut self) -> Option<Self::Item>
    Advances the iterator and returns the next value.

    Returns None when iteration is finished. Individual iterator implementations may
    choose to resume iteration, and so calling next() again may or may not eventually
    start returning Some(Item) again at some point.

#7

Thank you @Letheed. Well, before returning, to_inputs prints correctly the inputs I expect to be returned in the Option. What happends later, is that they are missing.

For what concerns the Iterator stopping or the check, well, that’s not a problem because of 1) early returns through ? in the case of the check 2) the Iterator works as expected, in fact “to_inputs inputs” print diplays correctly the inputs I expect to see.


#8

I’ve got the print of “inputs” before the print of the “to_inputs inputs”, which is the print internal to the function to_inputs diplaying the inputs to be returned. Just that.


#10
to_inputs inputs: []
oupoints.to_inputs(): Ok([])
to_inputs inputs: []
inputs: []

Isn’t that the part that you’re asking about?


#11


#12

Your problem is with the empty inputs right?


#13

Right, and I don’t understand why “inputs” is empty even if it should have the result of “to_inputs” as its own value. And as you can see “to_inputs” returned Vec is not empty.


#14

It is. You called to_inputs() twice, on lines 473 and 474. These calls are the ones that printed to_inputs inputs: [], twice.
The inputs are empty because inputs.push(input) in the for loop never gets called. Which is only possible if Input::new returned Err (but we know that’s not the case) or if the first call to next() in your for loop returns None.


#15

But have you noticed that the last (and relevant) print of inputs inside to_inputs() and after the for loop, returns a populated vector?

The 2 to_inputs where not an issue, it was on purpose, it’s the fact that inputs is printed before to_inputs() that got me thinking. But it’s a lateral issue. The issue is: why to_inputs() inputs is populated, while the inputs variable in the test, that should have as a value the moved result of to_inputs(), is empty?


#16

I don’t understand.

println!("outpoints: {:?}", outpoints);
println!("oupoints.to_inputs(): {:?}", outpoints.to_inputs());
let inputs = outpoints.to_inputs().unwrap();
println!("inputs: {:?}", inputs);

That code does exactly what is expected.
It prints this:

to_inputs inputs: []
oupoints.to_inputs(): Ok([])
to_inputs inputs: []
inputs: []

The last call to to_inputs() happens at line 489 in check_doublespending(). Obviously what you do in that function results in an Outpoints that does not trigger Err when self.check() is called on its items. Hence the inputs vec is full this time.


#17

Thank you for your reply @Letheed.

check_doublespending() (in mod tx) is not calling to_inputs(), cause it’s accessing directly the private field inputs. outpoints.to_raw() is not calling to_inputs() either, cause to_raw() accesses directly to the field items of struct OutPoints.


#18

Plus, @Letheed, even if it was the case that to_inputs's iterator returned None in the first execution, it would be quite strange that, with the same inputted arguments (they do not change during the test) gives a different result. That’s the why of my question on asynchronicity.


#19

Yes it does, here, on the outpoints that you created 2 lines above. Then it returns this error, which triggers the panic! in assert!(res.is_ok()).


#20

Your bug comes from the fact that in check_doublespending your create Outpoints from a Vec<Outpoint which sets length to vec.len() which is corrects whereas in your test you create outpoints with repeated calls to push in which you forgot to increment length. Hence check fails here.


#22

Thank you @Letheed, I had a 21 hours timeout, so I could not thank you decently. It was one of those epic fail style bugs :sweat_smile:

I’ve added the ref to your help in the commit msg: “Fix the lenght bug in iterators. @Letheed”, commit 724c4da3cda07174c74dc14df8f4a233ccf16e91. The minimum I could do.

Thank you again and have a nice day :slight_smile: