axvisor_api (Experimental Next-Generation Axvisor API)
> 中文README <
⚠️ This repository is experimental. The list and syntax of the API may change. ⚠️
⚠️ These APIs may eventually be stabilized or may be removed in the future. ⚠️
⚠️ The maintainers will do their best to maintain compatibility, but breaking changes may still occur. ⚠️
Why a Next-Generation API?
Various components of Axvisor need to access functionalities provided by the ArceOS unikernel. For the crate Axvisor itself, ArceOS is a dependency and can be accessed directly. However, to maintain decoupling, other lower-level components should not use ArceOS as a direct dependency and hence cannot directly access its API. Therefore, a form of "dependency injection" is required to provide ArceOS's API to various parts of Axvisor.
Currently, Axvisor mainly uses traits + generic parameters to implement dependency injection and provide APIs to components. For example:
// Component defines the API it needs
// Axvisor provides the implementation
;
This method has some obvious advantages:
- Very elegant, fully conforms to Rust's programming paradigm, no magic, easy to understand.
- Low coupling. In theory, any low-level component can be ported to any other kernel as long as the required API is provided.
However, there are also some disadvantages:
- The caller (struct or function) must carry all the generic parameters for the dependencies it uses, making the code verbose and less readable.
- Different traits inevitably have overlapping methods, causing redundancy.
- A common solution to the above two issues is to group APIs into some common traits. However, this increases nesting and coupling between traits. For example:
- The most serious problem: if a struct or function at the bottom of the dependency graph adds a new API dependency, all upstream type signatures must change to accommodate it, greatly increasing maintenance cost.
Design of the Next-Generation API
axvisor_api
aims to solve the above problems. Its design approach is:
- Use
crate_interface
to define the API interface and wrap the provided API into regular functions. - Organize APIs by module, with each module corresponding to a functional area and a
crate_interface
trait. - Each module can include API function definitions, type definitions, constants, and helper functions based on API functions.
Example code of axvisor_api
:
// Define an API module
// Implement the API module
// Use the API module
use ;
This approach replaces all previous traits with a unified, functionally categorized API collection and eliminates explicit trait dependencies via crate_interface
. Its advantages are:
- API function calls are just like regular functions — simpler to use and easier to understand.
- Callers don't need to worry about what APIs their dependencies require. Changes in dependencies don't affect callers.
- API modules can include types, constants, and helper functions, providing better organization.
Nonetheless, there are also some downsides:
- Although
crate_interface
uses traits under the hood for compile-time checks, these checks are weaker than traditional traits. For instance, if anapi_mod
is not implemented at all, the issue is only caught at link time. - This design doesn't allow providing two different API implementations for the same component via traits in the same program, slightly reducing flexibility. However, such cases are rare in Axvisor.
- Reduces the ability to reuse a single component independently. For instance, a component that could previously be reused directly now must import
axvisor_api
. This could be mitigated using feature flags to disable unused parts ofaxvisor_api
(not yet implemented), but it's still a downside.
Current Issues
Besides the above drawbacks, the current implementation of axvisor_api
has some issues:
- Does not support non-inline modules: The common approach of placing modules in separate files (e.g.,
#[api_mod] mod x;
withx.rs
) is currently unsupported due to limitations of Rust procedural macros. - Slight interference with IDE functionality: The use of procedural macros may slightly affect IDE features like autocomplete and jump-to-definition. However, rust-analyzer seems to work fine.
- Conflict between
extern fn
syntax and rustfmt: Marking API functions withextern fn
improves readability and consistency, but rustfmt rewrites it toextern "C" fn
, causing compile errors. A possible solution is to directly useextern "C" fn
, though this may clash with actual external C function declarations. - API functions not prominent enough: Since the only difference between API and normal functions is the
extern
keyword and the lack of a body, API functions may not stand out in large code blocks. Currently, detailed documentation is provided as a workaround. In the future, an#[api]
attribute might improve readability.
Additionally, there are some platform-related issues that are independent of the axvisor_api
design or implementation. For example:
- Platform-specific APIs: Some APIs are strongly tied to specific platforms or even specific devices but are essential. For example, on ARM, the semi-virtualized GIC implementation relies heavily on the physical GIC driver. Including all such functionality in
axvisor_api
makes the modules bloated, but not doing so could hurt readability and maintainability.