Hi, I've been working on wrapping libc::getifaddrs and I've gotten pretty far. However this is my first time writing unsafe Rust: please take a look at it and tell me how badly I've screwed up?
The primary entrypoint function is get_if_addrs which returns a Vec of Interface, which has a name and a Vec of InterfaceProto which contains addresses and flags.
Make this code compiling first, then silence compilers warnings (unused variables), then get rid of transmute.
I would wrap libc::ifaddrs some RAII type which would call libc::freeifaddrs(_) when it go out of scope. The wrapper would provide iteration via interface like this: fn iter<'a>(&'a self) -> impl Iterator<Item = &'a libc::ifaddrs>. This would let me rewrite all this nasty while loop into nice for.
Lines 123..129: reinvented wheel, you could let mut index = interfaces.position(|iface| name == iface.name). Also lines 123..139 and 170 is no Rusty at all... Why is this vector instead of HashMap<String, Vec<InterfaceProtocol>>? Using HashMap would definetly be more convinient here and simplify code much. If you thing in "cache effitiency" terms, stop - here you have heap allocations both in String and Vec, so this vector isn't cache friendly at all.
Lines 156..160: check match statement, would me much more elegant.
In general: this code looks more like C in Rust syntax, than real Rust code. Try to think more functional, and check interfaces you are using (in particular when you are trying to do something in iterator, check if Iterator trait hasn't helper for it).
The Option in arguments doesn't seem to have semantic purpose.
That might be subjective, but I'd return Option only for cases where the result is valid, and the correct result means there is nothing to return. For error cases, such as unsupported type, I'd return Result.
Instead of while !ptr_to_ifaddrs_current.is_null() you could have while let Some(p) = ptr_to_ifaddrs_current.as_ref(), which converts to a "safe" reference.
Ah, thanks. I'll change those as well.
What do you think of the new API design? Should I also provide a function that returns HashMap<String, InterfaceAddr> as well as the Iterator-based interface?
I don't think you need a function to return a HashMap<String, InterfaceAddr>. HashMap implements FromIterator so someone can use your iterator to create a key/value tuple and then collect them into a HashMap if they want.
One thing you should consider is propagating errors from libc to the user via Result return types. Right now it looks like you'll return None (i.e. an Option return type), but that will mask problems that a user may want to know about.
sockaddr:7..44 - there is function for this, Option::and_then:
unsafe { sockaddr_input.as_ref() }.and_then(|sa| {
match sa.sa_famili as i32 {
// ...
}
});
sockaddr:21..24 - as kornel said, unsafe blocks are also values, betterr would be:
let data_v4: &libc::sockaddr_in = unsafe { transmute(as) };
Same in 28..29, 35..38.
In lib:46 you ain't need this check, if its created in proper way, thats no way this would be null. Same in line 100. In general I would rather change InterfaceAddrs to this:
I have another problem I'd appreciate some advice on: I'm working on tests and documentation, and I'm not really sure how to test this library's behavior. It's totally reliant on external, unknowable state, by design.
Yeah that's going to be tricky. I suppose you can create fake ifaddrs and write some unit tests against them. For integration tests, provide a simple example main that someone can run on their machine and see the output. Setting up any sort of virtual environment/interfaces seems too heavyweight to me.