Rust 借用检查器定义了一组规则,以哪些可能的模式(可变或不可变)可以借用什么。它做得更多,即处理生命周期,如果你不借,你就搬家。
对于有抱负的 Rust 程序员来说,处理借用检查器强加的规则是痛苦的山谷(也就是学习曲线),他们必须通过才能变得富有成效。
一次,迭代器不可变地借用向量(并在循环期间保持它),借用检查器抱怨尝试变异。
如果你像我一样是一个横向思考者……每当我看到一种新语言的东西时,我都无法完全理解,我会尝试用我知道的语言写出那个“奇怪的东西”的教育近似已经好多了。
因此,希望它有所帮助,您可以找到一个非常简化和大胆的近似值,即 rust 在 C++ 中的作用(在编译时)。只有 - 在我的 C++ 代码中,它发生在运行时并且违反规则会导致抛出异常。
我们使用的实例的“状态”(std::vector<int32_t>
在本例中为 a),关于可变性以及根据我们仓促发明的一组借用规则(可能类似于也可能不类似于 rusts)我们仍然可以做什么,由不同的类型表示(Managed<T>::ConstRef
或Managed<T>::MutableRef
)。状态适用的范围是 lambda 函数的范围,它们用作“智能范围”。main()
在下面的代码中试图复制for i in vec.iter() { .. }
场景。
也许从这个角度看问题对某人有帮助。
#include <iostream>
#include <cstdint>
#include <vector>
#include <string>
#include <stdexcept>
#include <sstream>
template<class T>
void notNull(const T* p)
{
if(nullptr == p)
throw std::invalid_argument("pointer is null!");
};
enum class Demand : uint8_t
{ CONST
, MUTABLE
};
inline
std::string
exinfo
(const char* message
, const char * file
, int line
)
{
std::ostringstream os;
os << file << ":" << line << std::endl
<< message << std::endl;
return os.str();
}
class borrow_error
: public std::logic_error
{
public:
explicit borrow_error(const std::string& what_arg)
: logic_error(what_arg)
{}
explicit borrow_error(const char* what_arg)
: logic_error(what_arg)
{}
};
class mutable_borrow_after_const_borrow
: public borrow_error
{
public:
mutable_borrow_after_const_borrow(const char* file, int line)
: borrow_error
(exinfo("mutable borrow after const borrow",file,line))
{}
};
#define THROW_MUTABLE_BORROW_AFTER_CONST_BORROW \
throw mutable_borrow_after_const_borrow(__FILE__,__LINE__)
class const_borrow_after_mutable_borrow
:public borrow_error
{
public:
const_borrow_after_mutable_borrow(const char* file, int line)
: borrow_error
(exinfo("const borrow after mutable borrow",file,line))
{}
};
#define THROW_CONST_BORROW_AFTER_MUTABLE_BORROW \
throw const_borrow_after_mutable_borrow(__FILE__,__LINE__)
class multiple_mutable_borrows
:public borrow_error
{
public:
multiple_mutable_borrows(const char* file, int line)
: borrow_error
(exinfo("more than one mutable borrow",file,line))
{}
};
#define THROW_MULTIPLE_MUTABLE_BORROWS \
throw multiple_mutable_borrows(__FILE__,__LINE__)
class mutable_access_to_const_attempt
: public borrow_error
{
public:
mutable_access_to_const_attempt(const char* file, int line)
: borrow_error
(exinfo("request to mutate the immutable.",file,line))
{}
};
#define THROW_MUTABLE_ACCESS_TO_CONST_ATTEMPT \
throw mutable_access_to_const_attempt(__FILE__,__LINE__)
template <class T>
struct Managed
{
using this_type = Managed<T>;
using managed_type = T;
struct ConstRef
{
this_type * origin;
ConstRef(this_type *org, const char* file, int line)
: origin{org}
{
notNull(origin);
// if( 0 != origin->mutableAccess )
// {
// throw const_borrow_after_mutable_borrow(file,line);
// }
origin->constAccess++;
}
~ConstRef()
{
origin->constAccess--;
}
operator const T&()
{
return origin->instance;
}
};
struct MutableRef
{
this_type *origin;
MutableRef(this_type *org, const char* file, int line)
: origin{org}
{
notNull(origin);
if(origin->instanceMode == Demand::CONST)
{
throw mutable_access_to_const_attempt(file,line);
}
if( 0 != origin->constAccess )
{
throw mutable_borrow_after_const_borrow(file,line);
}
// also allow max 1 mutator
if( 0 != origin->mutableAccess )
{
throw multiple_mutable_borrows(file,line);
}
origin->mutableAccess++;
}
~MutableRef()
{
origin->mutableAccess--;
}
operator T&()
{
return origin->instance;
}
};
Demand instanceMode;
int32_t constAccess;
int32_t mutableAccess;
T& instance;
Managed(T& inst, Demand demand = Demand::CONST)
: instanceMode{demand}
, constAccess{0}
, mutableAccess{0}
, instance{inst}
{
}
};
template <typename T, class F>
auto
borrow_const
( T& instance
, F body
, const char * file
, int line
) -> void
{
typename T::ConstRef arg{&instance, file, line};
body(arg);
}
#define BORROW_CONST(inst,body) \
borrow_const((inst),(body),__FILE__,__LINE__)
template <typename T, class F>
auto
borrow_mut
( T& instance
, F body
, const char * file
, int line
) -> void
{
typename T::MutableRef arg{&instance, file, line};
body(arg);
};
#define BORROW_MUT(inst,body) \
borrow_mut((inst),(body),__FILE__,__LINE__)
using VecI32 = std::vector<int32_t>;
using ManagedVector = Managed<VecI32>;
int main(int argc, const char *argv[])
{
VecI32 myVector;
ManagedVector vec{myVector,Demand::MUTABLE};
try
{
BORROW_MUT
( vec
, [] (ManagedVector::MutableRef& vecRef) -> void
{
static_cast<VecI32&>(vecRef).push_back(1);
static_cast<VecI32&>(vecRef).push_back(2);
// during iteration, changing targets are bad...
// if you borrow the vector to an iterator,
// the "state" of the vector becomes 'immutable'.
BORROW_CONST
( *(vecRef.origin)
, [] (typename ManagedVector::ConstRef& vecRef) -> void
{
for( auto i : static_cast<const VecI32&>(vecRef) )
{
std::cout << i << std::endl;
// Enforced by the rust compiler established
// borrow rules,
// it is not allowed to borrow mut from something
// immutable.
// Thus, trying to change what we iterate over...
// should be prevented by the borrow checker.
BORROW_MUT
( *(vecRef.origin)
, [] (typename ManagedVector::MutableRef& vecRef)
-> void
{
// next line should throw!
// but our BORROW_MUT throws first.
static_cast<VecI32&>(vecRef).push_back(3);
});
}
});
});
}
catch(borrow_error& berr)
{
std::cout << "borrow error: " << berr.what() << std::endl;
}
catch(std::logic_error& lerr)
{
std::cout << "logic error: " << lerr.what() << std::endl;
}
catch(std::exception& ex)
{
std::cout << "std::exception: " << ex.what() << std::endl;
}
catch(...)
{
std::cout << "We are taking horrible, HORRIBLE damage!" << std::endl;
}
return 0;
}