TryInto can be used here to convert the slice into an array ref.
It returns a result because it needs to check the size, so you'll have to unwrap it, but since you already asserted the size, it shouldn't be an issue.
Since the array has statically known size, its reference is not consists of the fat ptr. You can replace the slice::from_raw_parts() call with &*(pkt_ptr as *const [u8; 64])
Even better, do not put non-unsafe functions in an unsafe block, and do not duplicate the length-checking part, either. Furthermore, libc::size_t is guaranteed to be the same as usize so you can use usize in FFI without a cast:
#[no_mangle]
pub extern "C" fn handle_packet(pkt_ptr: *const u8, pkt_len: usize) {
assert!(!pkt_ptr.is_null());
let s = unsafe { slice::from_raw_parts(pkt_ptr, pkt_len) };
let packet: &[u8; 64] = s.try_into().expect("invalid size");
}
Arrays are not FFI-safe, and even if they were, an &[T; N] isn't represented as a (pointer, length) pair of arguments since the length is implicit. Even slices can't be passed like this, because the slice is equivalent with a (pointer, length) pair which is not necessarily the same ABI-wise as passing separate pointer and length arguments.