borrowize 0.1.0

Derive borrowed view structs from owned Rust structs.
Documentation

borrowize

borrowize provides #[derive(View)], a small procedural macro for generating borrowed view structs from owned structs.

The macro is useful when an API wants to expose a cheap borrowed projection of an owned value without writing a second struct and conversion method by hand.

Quick Start

use borrowize::View;

#[derive(Debug, PartialEq, Eq)]
struct Tag(&'static str);

#[derive(View)]
struct User {
    id: u64,
    name: String,
    tags: Vec<Tag>,
}

let user = User {
    id: 7,
    name: "Ada".to_owned(),
    tags: vec![Tag("admin"), Tag("editor")],
};

let view = user.view();

assert_eq!(*view.id, 7);
assert_eq!(view.name, "Ada");
assert_eq!(view.tags, [Tag("admin"), Tag("editor")]);

This generates a sibling view type similar to:

struct UserView<'borrowize> {
    id: &'borrowize u64,
    name: &'borrowize str,
    tags: &'borrowize [Tag],
}

impl User {
    fn view(&self) -> UserView<'_> {
        UserView {
            id: &self.id,
            name: self.name.as_str(),
            tags: self.tags.as_slice(),
        }
    }
}

The generated lifetime is always named 'borrowize inside generated view type definitions. Source structs may not define a lifetime with that name.

Default Mapping

borrowize maps common owned field types to borrowed forms:

Source field type Generated view field type Generated expression
String &'borrowize str self.field.as_str()
Vec<T> &'borrowize [T] self.field.as_slice()
[T; N] &'borrowize [T] &self.field[..]
Box<T> &'borrowize T self.field.as_ref()
PathBuf &'borrowize std::path::Path self.field.as_path()
Option<T> Option<Borrowed<T>> self.field.as_ref().map(...)
&'a T &'a T self.field
other T &'borrowize T &self.field

Option<T> is mapped recursively, so Option<String> becomes Option<&'borrowize str> and Option<u64> becomes Option<&'borrowize u64>.

Type matching is syntactic. The macro recognises unqualified names and common standard-library qualified names such as std::string::String, alloc::string::String, std::vec::Vec, alloc::vec::Vec, std::option::Option, core::option::Option, std::boxed::Box, alloc::boxed::Box, and std::path::PathBuf. Type aliases are treated as unknown types and are borrowed as &T.

Struct Options

Struct-level options are written as #[borrowize(...)] on the source struct. Names are explicit; there are no shorthand options.

use borrowize::View;

#[derive(View)]
#[borrowize(
    view_name = "UserParts",
    view_visibility = "pub",
    field_visibility = "pub",
    method_name = "as_parts",
    method_visibility = "pub"
)]
pub struct User {
    id: u64,
    name: String,
}

Supported struct options:

Option Meaning
view_name = "Name" Override the generated view type name.
view_visibility = "pub" Override the generated view type visibility.
field_visibility = "pub" Set the default visibility for generated view fields.
method_name = "name" Override the generated method name.
method_visibility = "pub" Override the generated method visibility.
no_method Do not generate the inherent view method.

When a visibility option is omitted, visibility is inherited from the source item or field. Explicit visibility values must be legal Rust visibility syntax, such as pub, pub(crate), or pub(super).

no_method cannot be combined with method_name or method_visibility.

Field Options

Field-level options are written as #[borrowize(...)] on a source field.

use borrowize::View;

#[derive(View)]
struct Encoded {
    #[borrowize(
        borrowed_type = "&'borrowize str",
        generation_expression = "::std::str::from_utf8(&self.bytes).expect(\"valid utf8\")"
    )]
    bytes: Vec<u8>,
}

Supported field options:

Option Meaning
borrowed_type = "Type" Override the generated view field type.
generation_expression = "expr" Override the generated field initializer in the view method.
visibility = "pub" Override this generated view field's visibility.

borrowed_type overrides may refer to the generated borrow lifetime as 'borrowize.

generation_expression is parsed as a Rust expression in the generated method body. It can refer to self because the generated method borrows &self.

Manual View Construction

Generated view fields inherit source field visibility by default. Use field_visibility or field-level visibility when views need to be assembled outside the source module.

use borrowize::View;

#[derive(View)]
#[borrowize(view_visibility = "pub", field_visibility = "pub", no_method)]
pub struct PublicSource {
    id: u64,
    name: String,
}

let id = 21;
let view = PublicSourceView {
    id: &id,
    name: "manual",
};

This is useful when you have borrowed pieces available before you have an owned source value to call view() on.

Generics

Source generics, const generics, lifetimes, and where clauses are preserved on the generated view type and inherent method.

use borrowize::View;

#[derive(View)]
struct Generic<'a, T, const N: usize>
where
    T: 'a,
{
    name: String,
    items: Vec<T>,
    borrowed: &'a T,
    fixed: [u8; N],
}

Current Limitations

View currently supports named-field structs only. Enums, unions, tuple structs, and unit structs produce compile errors.

The generated inherent method can conflict with a user-defined method of the same name. Use method_name to choose a different method name or no_method when only the view type is needed.

Nested custom view conversion is not inferred. Unknown types are borrowed as &T, even if they also derive View.

Type matching is syntactic, so aliases and re-exports of recognised container types are not expanded.

The generated lifetime name 'borrowize is reserved.

Licence

borrowize is distributed under the terms of both the MIT licence and the Apache License, Version 2.0. You may use it under either licence, at your option.

See LICENSE-MIT and LICENSE-APACHE for the full licence texts.

Contributions

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in borrowize is licensed as MIT OR Apache-2.0, without any additional terms or conditions.