mirror of
https://github.com/rust-lang/rust-clippy
synced 2025-02-16 05:58:41 +00:00
Extract get_vec_init_kind and share it
This commit is contained in:
parent
fec20bf617
commit
dd9c8d32f2
4 changed files with 105 additions and 112 deletions
|
@ -1,24 +1,24 @@
|
||||||
use clippy_utils::diagnostics::span_lint_and_then;
|
use clippy_utils::diagnostics::span_lint_and_then;
|
||||||
|
use clippy_utils::get_vec_init_kind;
|
||||||
use clippy_utils::ty::is_type_diagnostic_item;
|
use clippy_utils::ty::is_type_diagnostic_item;
|
||||||
use clippy_utils::{
|
use clippy_utils::{path_to_local_id, peel_hir_expr_while, ty::is_uninit_value_valid_for_ty, SpanlessEq};
|
||||||
match_def_path, path_to_local_id, paths, peel_hir_expr_while, ty::is_uninit_value_valid_for_ty, SpanlessEq,
|
use rustc_hir::{Block, Expr, ExprKind, HirId, PatKind, PathSegment, Stmt, StmtKind};
|
||||||
};
|
|
||||||
use rustc_hir::def::Res;
|
|
||||||
use rustc_hir::{Block, Expr, ExprKind, HirId, PatKind, Stmt, StmtKind};
|
|
||||||
use rustc_lint::{LateContext, LateLintPass};
|
use rustc_lint::{LateContext, LateLintPass};
|
||||||
use rustc_middle::ty;
|
use rustc_middle::ty;
|
||||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||||
use rustc_span::{sym, Span};
|
use rustc_span::{sym, Span};
|
||||||
|
|
||||||
|
// TODO: add `ReadBuf` (RFC 2930) in "How to fix" once it is available in std
|
||||||
declare_clippy_lint! {
|
declare_clippy_lint! {
|
||||||
/// ### What it does
|
/// ### What it does
|
||||||
/// Checks for the creation of uninitialized `Vec<T>` by calling `set_len()`
|
/// Checks for `set_len()` call that creates `Vec` with uninitialized elements.
|
||||||
/// immediately after `with_capacity()` or `reserve()`.
|
/// This is commonly caused by calling `set_len()` right after after calling
|
||||||
|
/// `with_capacity()` or `reserve()`.
|
||||||
///
|
///
|
||||||
/// ### Why is this bad?
|
/// ### Why is this bad?
|
||||||
/// It creates `Vec<T>` that contains uninitialized data, which leads to an
|
/// It creates a `Vec` with uninitialized data, which leads to an
|
||||||
/// undefined behavior with most safe operations.
|
/// undefined behavior with most safe operations.
|
||||||
/// Notably, using uninitialized `Vec<u8>` with generic `Read` is unsound.
|
/// Notably, uninitialized `Vec<u8>` must not be used with generic `Read`.
|
||||||
///
|
///
|
||||||
/// ### Example
|
/// ### Example
|
||||||
/// ```rust,ignore
|
/// ```rust,ignore
|
||||||
|
@ -26,16 +26,25 @@ declare_clippy_lint! {
|
||||||
/// unsafe { vec.set_len(1000); }
|
/// unsafe { vec.set_len(1000); }
|
||||||
/// reader.read(&mut vec); // undefined behavior!
|
/// reader.read(&mut vec); // undefined behavior!
|
||||||
/// ```
|
/// ```
|
||||||
/// Use an initialized buffer:
|
///
|
||||||
/// ```rust,ignore
|
/// ### How to fix?
|
||||||
/// let mut vec: Vec<u8> = vec![0; 1000];
|
/// 1. Use an initialized buffer:
|
||||||
/// reader.read(&mut vec);
|
/// ```rust,ignore
|
||||||
/// ```
|
/// let mut vec: Vec<u8> = vec![0; 1000];
|
||||||
/// Or, wrap the content in `MaybeUninit`:
|
/// reader.read(&mut vec);
|
||||||
/// ```rust,ignore
|
/// ```
|
||||||
/// let mut vec: Vec<MaybeUninit<T>> = Vec::with_capacity(1000);
|
/// 2. Wrap the content in `MaybeUninit`:
|
||||||
/// unsafe { vec.set_len(1000); }
|
/// ```rust,ignore
|
||||||
/// ```
|
/// let mut vec: Vec<MaybeUninit<T>> = Vec::with_capacity(1000);
|
||||||
|
/// vec.set_len(1000); // `MaybeUninit` can be uninitialized
|
||||||
|
/// ```
|
||||||
|
/// 3. If you are on nightly, `Vec::spare_capacity_mut()` is available:
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// let mut vec: Vec<u8> = Vec::with_capacity(1000);
|
||||||
|
/// let remaining = vec.spare_capacity_mut(); // `&mut [MaybeUninit<u8>]`
|
||||||
|
/// // perform initialization with `remaining`
|
||||||
|
/// vec.set_len(...); // Safe to call `set_len()` on initialized part
|
||||||
|
/// ```
|
||||||
pub UNINIT_VEC,
|
pub UNINIT_VEC,
|
||||||
correctness,
|
correctness,
|
||||||
"Vec with uninitialized data"
|
"Vec with uninitialized data"
|
||||||
|
@ -59,11 +68,11 @@ impl<'tcx> LateLintPass<'tcx> for UninitVec {
|
||||||
|
|
||||||
fn handle_uninit_vec_pair(
|
fn handle_uninit_vec_pair(
|
||||||
cx: &LateContext<'tcx>,
|
cx: &LateContext<'tcx>,
|
||||||
maybe_with_capacity_or_reserve: &'tcx Stmt<'tcx>,
|
maybe_init_or_reserve: &'tcx Stmt<'tcx>,
|
||||||
maybe_set_len: &'tcx Expr<'tcx>,
|
maybe_set_len: &'tcx Expr<'tcx>,
|
||||||
) {
|
) {
|
||||||
if_chain! {
|
if_chain! {
|
||||||
if let Some(vec) = extract_with_capacity_or_reserve_target(cx, maybe_with_capacity_or_reserve);
|
if let Some(vec) = extract_init_or_reserve_target(cx, maybe_init_or_reserve);
|
||||||
if let Some((set_len_self, call_span)) = extract_set_len_self(cx, maybe_set_len);
|
if let Some((set_len_self, call_span)) = extract_set_len_self(cx, maybe_set_len);
|
||||||
if vec.eq_expr(cx, set_len_self);
|
if vec.eq_expr(cx, set_len_self);
|
||||||
if let ty::Ref(_, vec_ty, _) = cx.typeck_results().expr_ty_adjusted(set_len_self).kind();
|
if let ty::Ref(_, vec_ty, _) = cx.typeck_results().expr_ty_adjusted(set_len_self).kind();
|
||||||
|
@ -71,12 +80,12 @@ fn handle_uninit_vec_pair(
|
||||||
// Check T of Vec<T>
|
// Check T of Vec<T>
|
||||||
if !is_uninit_value_valid_for_ty(cx, substs.type_at(0));
|
if !is_uninit_value_valid_for_ty(cx, substs.type_at(0));
|
||||||
then {
|
then {
|
||||||
// FIXME: false positive #7698
|
// FIXME: #7698, false positive of the internal lints
|
||||||
#[allow(clippy::collapsible_span_lint_calls)]
|
#[allow(clippy::collapsible_span_lint_calls)]
|
||||||
span_lint_and_then(
|
span_lint_and_then(
|
||||||
cx,
|
cx,
|
||||||
UNINIT_VEC,
|
UNINIT_VEC,
|
||||||
vec![call_span, maybe_with_capacity_or_reserve.span],
|
vec![call_span, maybe_init_or_reserve.span],
|
||||||
"calling `set_len()` immediately after reserving a buffer creates uninitialized values",
|
"calling `set_len()` immediately after reserving a buffer creates uninitialized values",
|
||||||
|diag| {
|
|diag| {
|
||||||
diag.help("initialize the buffer or wrap the content in `MaybeUninit`");
|
diag.help("initialize the buffer or wrap the content in `MaybeUninit`");
|
||||||
|
@ -101,15 +110,15 @@ impl<'tcx> LocalOrExpr<'tcx> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the target vec of `Vec::with_capacity()` or `Vec::reserve()`
|
/// Finds the target location where the result of `Vec` initialization is stored
|
||||||
fn extract_with_capacity_or_reserve_target(cx: &LateContext<'_>, stmt: &'tcx Stmt<'_>) -> Option<LocalOrExpr<'tcx>> {
|
/// or `self` expression for `Vec::reserve()`.
|
||||||
|
fn extract_init_or_reserve_target<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) -> Option<LocalOrExpr<'tcx>> {
|
||||||
match stmt.kind {
|
match stmt.kind {
|
||||||
StmtKind::Local(local) => {
|
StmtKind::Local(local) => {
|
||||||
// let mut x = Vec::with_capacity()
|
|
||||||
if_chain! {
|
if_chain! {
|
||||||
if let Some(init_expr) = local.init;
|
if let Some(init_expr) = local.init;
|
||||||
if let PatKind::Binding(_, hir_id, _, None) = local.pat.kind;
|
if let PatKind::Binding(_, hir_id, _, None) = local.pat.kind;
|
||||||
if is_with_capacity(cx, init_expr);
|
if get_vec_init_kind(cx, init_expr).is_some();
|
||||||
then {
|
then {
|
||||||
Some(LocalOrExpr::Local(hir_id))
|
Some(LocalOrExpr::Local(hir_id))
|
||||||
} else {
|
} else {
|
||||||
|
@ -117,40 +126,20 @@ fn extract_with_capacity_or_reserve_target(cx: &LateContext<'_>, stmt: &'tcx Stm
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
StmtKind::Expr(expr) | StmtKind::Semi(expr) => {
|
StmtKind::Expr(expr) | StmtKind::Semi(expr) => match expr.kind {
|
||||||
match expr.kind {
|
ExprKind::Assign(lhs, rhs, _span) if get_vec_init_kind(cx, rhs).is_some() => Some(LocalOrExpr::Expr(lhs)),
|
||||||
ExprKind::Assign(lhs, rhs, _span) if is_with_capacity(cx, rhs) => {
|
ExprKind::MethodCall(path, _, [self_expr, _], _) if is_reserve(cx, path, self_expr) => {
|
||||||
// self.vec = Vec::with_capacity()
|
Some(LocalOrExpr::Expr(self_expr))
|
||||||
Some(LocalOrExpr::Expr(lhs))
|
},
|
||||||
},
|
_ => None,
|
||||||
ExprKind::MethodCall(path, _, [self_expr, _], _) => {
|
|
||||||
// self.vec.reserve()
|
|
||||||
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr).peel_refs(), sym::vec_type)
|
|
||||||
&& path.ident.name.as_str() == "reserve"
|
|
||||||
{
|
|
||||||
Some(LocalOrExpr::Expr(self_expr))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
StmtKind::Item(_) => None,
|
StmtKind::Item(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_with_capacity(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> bool {
|
fn is_reserve(cx: &LateContext<'_>, path: &PathSegment<'_>, self_expr: &Expr<'_>) -> bool {
|
||||||
if_chain! {
|
is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr).peel_refs(), sym::Vec)
|
||||||
if let ExprKind::Call(path_expr, _) = &expr.kind;
|
&& path.ident.name.as_str() == "reserve"
|
||||||
if let ExprKind::Path(qpath) = &path_expr.kind;
|
|
||||||
if let Res::Def(_, def_id) = cx.qpath_res(qpath, path_expr.hir_id);
|
|
||||||
then {
|
|
||||||
match_def_path(cx, def_id, &paths::VEC_WITH_CAPACITY)
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns self if the expression is `Vec::set_len()`
|
/// Returns self if the expression is `Vec::set_len()`
|
||||||
|
@ -169,14 +158,13 @@ fn extract_set_len_self(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Option<(&
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
match expr.kind {
|
match expr.kind {
|
||||||
ExprKind::MethodCall(_, _, [vec_expr, _], _) => {
|
ExprKind::MethodCall(path, _, [self_expr, _], _) => {
|
||||||
cx.typeck_results().type_dependent_def_id(expr.hir_id).and_then(|id| {
|
let self_type = cx.typeck_results().expr_ty(self_expr).peel_refs();
|
||||||
if match_def_path(cx, id, &paths::VEC_SET_LEN) {
|
if is_type_diagnostic_item(cx, self_type, sym::Vec) && path.ident.name.as_str() == "set_len" {
|
||||||
Some((vec_expr, expr.span))
|
Some((self_expr, expr.span))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
|
||||||
},
|
},
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||||
use clippy_utils::source::snippet;
|
use clippy_utils::source::snippet;
|
||||||
use clippy_utils::ty::is_type_diagnostic_item;
|
use clippy_utils::{get_vec_init_kind, path_to_local, path_to_local_id, VecInitKind};
|
||||||
use clippy_utils::{match_def_path, path_to_local, path_to_local_id, paths};
|
|
||||||
use if_chain::if_chain;
|
use if_chain::if_chain;
|
||||||
use rustc_ast::ast::LitKind;
|
|
||||||
use rustc_errors::Applicability;
|
use rustc_errors::Applicability;
|
||||||
use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, Local, PatKind, QPath, Stmt, StmtKind};
|
use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, Local, PatKind, Stmt, StmtKind};
|
||||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||||
use rustc_middle::lint::in_external_macro;
|
use rustc_middle::lint::in_external_macro;
|
||||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||||
use rustc_span::{symbol::sym, Span};
|
use rustc_span::Span;
|
||||||
use std::convert::TryInto;
|
|
||||||
|
|
||||||
declare_clippy_lint! {
|
declare_clippy_lint! {
|
||||||
/// ### What it does
|
/// ### What it does
|
||||||
|
@ -41,11 +38,6 @@ pub struct VecInitThenPush {
|
||||||
searcher: Option<VecPushSearcher>,
|
searcher: Option<VecPushSearcher>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
enum VecInitKind {
|
|
||||||
New,
|
|
||||||
WithCapacity(u64),
|
|
||||||
}
|
|
||||||
struct VecPushSearcher {
|
struct VecPushSearcher {
|
||||||
local_id: HirId,
|
local_id: HirId,
|
||||||
init: VecInitKind,
|
init: VecInitKind,
|
||||||
|
@ -58,7 +50,8 @@ impl VecPushSearcher {
|
||||||
fn display_err(&self, cx: &LateContext<'_>) {
|
fn display_err(&self, cx: &LateContext<'_>) {
|
||||||
match self.init {
|
match self.init {
|
||||||
_ if self.found == 0 => return,
|
_ if self.found == 0 => return,
|
||||||
VecInitKind::WithCapacity(x) if x > self.found => return,
|
VecInitKind::WithLiteralCapacity(x) if x > self.found => return,
|
||||||
|
VecInitKind::WithExprCapacity(_) => return,
|
||||||
_ => (),
|
_ => (),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -152,37 +145,3 @@ impl LateLintPass<'_> for VecInitThenPush {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<VecInitKind> {
|
|
||||||
if let ExprKind::Call(func, args) = expr.kind {
|
|
||||||
match func.kind {
|
|
||||||
ExprKind::Path(QPath::TypeRelative(ty, name))
|
|
||||||
if is_type_diagnostic_item(cx, cx.typeck_results().node_type(ty.hir_id), sym::Vec) =>
|
|
||||||
{
|
|
||||||
if name.ident.name == sym::new {
|
|
||||||
return Some(VecInitKind::New);
|
|
||||||
} else if name.ident.name.as_str() == "with_capacity" {
|
|
||||||
return args.get(0).and_then(|arg| {
|
|
||||||
if_chain! {
|
|
||||||
if let ExprKind::Lit(lit) = &arg.kind;
|
|
||||||
if let LitKind::Int(num, _) = lit.node;
|
|
||||||
then {
|
|
||||||
Some(VecInitKind::WithCapacity(num.try_into().ok()?))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ExprKind::Path(QPath::Resolved(_, path))
|
|
||||||
if match_def_path(cx, path.res.opt_def_id()?, &paths::DEFAULT_TRAIT_METHOD)
|
|
||||||
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Vec) =>
|
|
||||||
{
|
|
||||||
return Some(VecInitKind::New);
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
|
@ -93,7 +93,7 @@ use rustc_span::{Span, DUMMY_SP};
|
||||||
use rustc_target::abi::Integer;
|
use rustc_target::abi::Integer;
|
||||||
|
|
||||||
use crate::consts::{constant, Constant};
|
use crate::consts::{constant, Constant};
|
||||||
use crate::ty::{can_partially_move_ty, is_copy, is_recursively_primitive_type};
|
use crate::ty::{can_partially_move_ty, is_copy, is_recursively_primitive_type, is_type_diagnostic_item};
|
||||||
|
|
||||||
pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Option<RustcVersion> {
|
pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Option<RustcVersion> {
|
||||||
if let Ok(version) = RustcVersion::parse(msrv) {
|
if let Ok(version) = RustcVersion::parse(msrv) {
|
||||||
|
@ -1789,6 +1789,53 @@ pub fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum VecInitKind {
|
||||||
|
/// `Vec::new()`
|
||||||
|
New,
|
||||||
|
/// `Vec::default()` or `Default::default()`
|
||||||
|
Default,
|
||||||
|
/// `Vec::with_capacity(123)`
|
||||||
|
WithLiteralCapacity(u64),
|
||||||
|
/// `Vec::with_capacity(slice.len())`
|
||||||
|
WithExprCapacity(HirId),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if given expression is an initialization of `Vec` and returns its kind.
|
||||||
|
pub fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<VecInitKind> {
|
||||||
|
if let ExprKind::Call(func, args) = expr.kind {
|
||||||
|
match func.kind {
|
||||||
|
ExprKind::Path(QPath::TypeRelative(ty, name))
|
||||||
|
if is_type_diagnostic_item(cx, cx.typeck_results().node_type(ty.hir_id), sym::Vec) =>
|
||||||
|
{
|
||||||
|
if name.ident.name == sym::new {
|
||||||
|
return Some(VecInitKind::New);
|
||||||
|
} else if name.ident.name.as_str() == "with_capacity" {
|
||||||
|
return args.get(0).and_then(|arg| {
|
||||||
|
if_chain! {
|
||||||
|
if let ExprKind::Lit(lit) = &arg.kind;
|
||||||
|
if let LitKind::Int(num, _) = lit.node;
|
||||||
|
then {
|
||||||
|
Some(VecInitKind::WithLiteralCapacity(num.try_into().ok()?))
|
||||||
|
} else {
|
||||||
|
Some(VecInitKind::WithExprCapacity(arg.hir_id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ExprKind::Path(QPath::Resolved(_, path))
|
||||||
|
if match_def_path(cx, path.res.opt_def_id()?, &paths::DEFAULT_TRAIT_METHOD)
|
||||||
|
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Vec) =>
|
||||||
|
{
|
||||||
|
return Some(VecInitKind::Default);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets the node where an expression is either used, or it's type is unified with another branch.
|
/// Gets the node where an expression is either used, or it's type is unified with another branch.
|
||||||
pub fn get_expr_use_or_unification_node(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<Node<'tcx>> {
|
pub fn get_expr_use_or_unification_node(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<Node<'tcx>> {
|
||||||
let mut child_id = expr.hir_id;
|
let mut child_id = expr.hir_id;
|
||||||
|
|
|
@ -183,7 +183,6 @@ pub const VEC_AS_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_slice"];
|
||||||
pub const VEC_FROM_ELEM: [&str; 3] = ["alloc", "vec", "from_elem"];
|
pub const VEC_FROM_ELEM: [&str; 3] = ["alloc", "vec", "from_elem"];
|
||||||
pub const VEC_NEW: [&str; 4] = ["alloc", "vec", "Vec", "new"];
|
pub const VEC_NEW: [&str; 4] = ["alloc", "vec", "Vec", "new"];
|
||||||
pub const VEC_RESIZE: [&str; 4] = ["alloc", "vec", "Vec", "resize"];
|
pub const VEC_RESIZE: [&str; 4] = ["alloc", "vec", "Vec", "resize"];
|
||||||
pub const VEC_WITH_CAPACITY: [&str; 4] = ["alloc", "vec", "Vec", "with_capacity"];
|
|
||||||
pub const VEC_SET_LEN: [&str; 4] = ["alloc", "vec", "Vec", "set_len"];
|
pub const VEC_SET_LEN: [&str; 4] = ["alloc", "vec", "Vec", "set_len"];
|
||||||
pub const WEAK_ARC: [&str; 3] = ["alloc", "sync", "Weak"];
|
pub const WEAK_ARC: [&str; 3] = ["alloc", "sync", "Weak"];
|
||||||
pub const WEAK_RC: [&str; 3] = ["alloc", "rc", "Weak"];
|
pub const WEAK_RC: [&str; 3] = ["alloc", "rc", "Weak"];
|
||||||
|
|
Loading…
Add table
Reference in a new issue