diff --git a/util/script.c b/util/script.c
index ba05d0e..f81380c 100644
--- a/util/script.c
+++ b/util/script.c
@@ -167,6 +167,8 @@
 #define T_OP_SUCCESS          (160)
 #define T_OP_ERROR            (161)
 #define T_OP_DEFAULT          (162)
+#define T_OP_INT_TO_FLOAT     (163)
+#define T_OP_FLOAT_TRUNCATE   (164)
 
 #define T_IF                  (170)
 #define T_WHILE               (171)
@@ -197,6 +199,7 @@
 #define T_SUCCESS             (196)
 #define T_RETERR              (197)
 #define T_INTTYPE             (198)
+#define T_HANDLETYPE          (199)
 
 #define STACK_READ_STRING(textVariable, bytesVariable, stackIndex) \
 	if (context->c->stackPointer < stackIndex) return -1; \
@@ -254,7 +257,7 @@ typedef struct Scope {
 
 typedef struct Node {
 	uint8_t type;
-	bool referencesRootScope, isExternalCall, isPersistentVariable, isOptionVariable;
+	bool referencesRootScope, isExternalCall, isPersistentVariable, isOptionVariable, cycleCheck;
 	uint8_t operationType;
 	int32_t inlineImportVariableIndex;
 	Token token;
@@ -976,6 +979,7 @@ Token TokenNext(Tokenizer *tokenizer) {
 			else if KEYWORD("float") token.type = T_FLOAT;
 			else if KEYWORD("for") token.type = T_FOR;
 			else if KEYWORD("functype") token.type = T_FUNCTYPE;
+			else if KEYWORD("handletype") token.type = T_HANDLETYPE;
 			else if KEYWORD("if") token.type = T_IF;
 			else if KEYWORD("in") token.type = T_IN;
 			else if KEYWORD("int") token.type = T_INT;
@@ -1422,7 +1426,7 @@ Node *ParseExpression(Tokenizer *tokenizer, bool allowAssignment, uint8_t preced
 			TokenNext(tokenizer);
 			Token operationName = TokenNext(tokenizer);
 
-			if (operationName.type != T_IDENTIFIER && operationName.type != T_ASSERT) {
+			if (operationName.type != T_IDENTIFIER && operationName.type != T_ASSERT && operationName.type != T_FLOAT) {
 				PrintError2(tokenizer, node, "Expected an identifier for the operation name after ':'.\n");
 				return NULL;
 			}
@@ -2095,6 +2099,31 @@ Node *ParseRoot(Tokenizer *tokenizer) {
 				PrintError2(tokenizer, node->firstChild->sibling, "Expected a semicolon after the argument list.\n");
 				return NULL;
 			}
+		} else if (token.type == T_HANDLETYPE) {
+			TokenNext(tokenizer);
+			Node *node = (Node *) AllocateFixed(sizeof(Node));
+			node->type = T_HANDLETYPE;
+			node->token = TokenNext(tokenizer);
+			*link = node;
+			link = &node->sibling;
+
+			if (node->token.type != T_IDENTIFIER) {
+				PrintError2(tokenizer, node, "Expected the name of the handletype.\n");
+				return NULL;
+			}
+
+			Token semicolon = TokenNext(tokenizer);
+
+			if (semicolon.type == T_COLON) {
+				node->firstChild = ParseType(tokenizer, false, false, false);
+				if (!node->firstChild) return NULL;
+				semicolon = TokenNext(tokenizer);
+			}
+
+			if (semicolon.type != T_SEMICOLON) {
+				PrintError2(tokenizer, node, "Expected a semicolon after the handletype identifier.\n");
+				return NULL;
+			}
 		} else if (token.type == T_INTTYPE) {
 			TokenNext(tokenizer);
 			Node *node = (Node *) AllocateFixed(sizeof(Node));
@@ -2423,7 +2452,7 @@ bool ASTSetScopes(Tokenizer *tokenizer, ExecutionContext *context, Node *node, S
 	}
 
 	if ((node->type == T_DECLARE || node->type == T_FUNCTION || node->type == T_FUNCTYPE || node->type == T_INTTYPE || node->type == T_INTTYPE_CONSTANT
-			|| node->type == T_STRUCT || node->type == T_IMPORT) && node->parent->type != T_STRUCT) {
+			|| node->type == T_STRUCT || node->type == T_IMPORT || node->type == T_HANDLETYPE) && node->parent->type != T_STRUCT) {
 		if (!ScopeAddEntry(tokenizer, scope, node)) {
 			return false;
 		}
@@ -2551,8 +2580,8 @@ bool ASTMatching(Node *left, Node *right) {
 		return true;
 	} else if (left->type != right->type) {
 		return false;
-	} else if ((left->type == T_IDENTIFIER || left->type == T_STRUCT) 
-			&& (left->token.textBytes != right->token.textBytes 
+	} else if ((left->type == T_IDENTIFIER || left->type == T_STRUCT || left->type == T_HANDLETYPE || left->type == T_INTTYPE) 
+			&& (left->token.module != right->token.module || left->token.textBytes != right->token.textBytes 
 				|| MemoryCompare(left->token.text, right->token.text, right->token.textBytes))) {
 		return false;
 	} else {
@@ -2615,17 +2644,22 @@ bool ASTLookupTypeIdentifiers(Tokenizer *tokenizer, Node *node) {
 		}
 	}
 
-	if (node->type == T_DECLARE || node->type == T_ARGUMENT || node->type == T_NEW || node->type == T_LIST) {
+	if (node->type == T_DECLARE || node->type == T_ARGUMENT || node->type == T_NEW || node->type == T_LIST || node->type == T_HANDLETYPE) {
 		Node *type = node->firstChild;
 
-		if (type->type == T_IDENTIFIER) {
+		if (type && type->type == T_IDENTIFIER) {
 			Node *lookup = ScopeLookup(tokenizer, type, false);
 
 			if (!lookup) {
 				return false;
 			}
 
-			if (!node->expressionType) {
+			if (node->type == T_HANDLETYPE) {
+				if (lookup->type != T_HANDLETYPE) {
+					PrintError2(tokenizer, lookup, "handletypes can only inherit from other handletypes.\n");
+					return false;
+				}
+			} else if (!node->expressionType) {
 				node->expressionType = node->firstChild;
 			}
 
@@ -2635,12 +2669,24 @@ bool ASTLookupTypeIdentifiers(Tokenizer *tokenizer, Node *node) {
 				return false;
 			} else if (lookup->type == T_FUNCTYPE) {
 				MemoryCopy(node->expressionType, lookup->firstChild, sizeof(Node));
-			} else if (lookup->type == T_STRUCT) {
-				MemoryCopy(node->expressionType, lookup, sizeof(Node));
-			} else if (lookup->type == T_INTTYPE) {
-				MemoryCopy(node->expressionType, lookup, sizeof(Node));
+			} else if (lookup->type == T_STRUCT || lookup->type == T_INTTYPE || lookup->type == T_HANDLETYPE) {
+				if (node->type == T_HANDLETYPE) {
+					if (lookup->cycleCheck) {
+						PrintError2(tokenizer, lookup, "Cyclic handletype inheritance.\n");
+						return false;
+					}
+
+					Node *copy = (Node *) AllocateFixed(sizeof(Node));
+					*copy = *lookup;
+					copy->sibling = NULL;
+					node->firstChild = copy;
+					node->cycleCheck = true;
+					if (!ASTLookupTypeIdentifiers(tokenizer, copy)) return false;
+				} else {
+					MemoryCopy(node->expressionType, lookup, sizeof(Node));
+				}
 			} else {
-				PrintError2(tokenizer, node, "The identifier did not resolve to a type.\n");
+				PrintError2(tokenizer, lookup, "The identifier did not resolve to a type.\n");
 				return false;
 			}
 
@@ -2651,18 +2697,38 @@ bool ASTLookupTypeIdentifiers(Tokenizer *tokenizer, Node *node) {
 	return true;
 }
 
-Node *ASTImplicitCastToErr(Tokenizer *tokenizer, Node *parent, Node *expression) {
-	if (!expression->expressionType || ASTMatching(expression->expressionType, &globalExpressionTypeVoid)) {
-		PrintError2(tokenizer, parent, "You cannot assign a value of 'void' to 'err[void]'.\nInstead, use the constant '#success'.\n");
+bool ASTImplicitCastIsPossible(Node *targetType, Node *expressionType) {
+	if (!expressionType || !targetType) {
+		return false;
+	} else if (targetType->type == T_ERR && ASTMatching(targetType->firstChild, expressionType)) {
+		return true;
+	} else if (targetType->type == T_HANDLETYPE && expressionType->type == T_HANDLETYPE) {
+		Node *inherit = expressionType->firstChild;
+		return inherit && (ASTMatching(targetType, inherit) || ASTImplicitCastIsPossible(targetType, inherit));
+	} else {
+		return false;
+	}
+}
+
+Node *ASTImplicitCastApply(Tokenizer *tokenizer, Node *parent, Node *target, Node *expression) {
+	if (target->type == T_ERR && ASTMatching(target->firstChild, expression->expressionType)) {
+		if (!expression->expressionType || ASTMatching(expression->expressionType, &globalExpressionTypeVoid)) {
+			PrintError2(tokenizer, parent, "You cannot assign a value of 'void' to 'err[void]'.\nInstead, use the constant '#success'.\n");
+			return NULL;
+		}
+
+		Node *cast = (Node *) AllocateFixed(sizeof(Node));
+		cast->type = T_ERR_CAST;
+		cast->scope = parent->scope;
+		cast->parent = parent;
+		cast->firstChild = expression;
+		return cast;
+	} else if (target->type == T_HANDLETYPE && expression->expressionType->type == T_HANDLETYPE) {
+		return expression; // Nothing needs to be done to cast between handletypes.
+	} else {
+		Assert(false);
 		return NULL;
 	}
-
-	Node *cast = (Node *) AllocateFixed(sizeof(Node));
-	cast->type = T_ERR_CAST;
-	cast->scope = parent->scope;
-	cast->parent = parent;
-	cast->firstChild = expression;
-	return cast;
 }
 
 Value ASTNumericLiteralToValue(Node *node) {
@@ -2787,6 +2853,7 @@ bool ASTSetTypes(Tokenizer *tokenizer, Node *node) {
 	Node *child = node->firstChild;
 
 	while (child) {
+		if (child == node) Assert(false);
 		if (!ASTSetTypes(tokenizer, child)) return false;
 		child->parent = node;
 		child = child->sibling;
@@ -2797,7 +2864,8 @@ bool ASTSetTypes(Tokenizer *tokenizer, Node *node) {
 			|| node->type == T_LIST || node->type == T_TUPLE || node->type == T_ERR
 			|| node->type == T_BOOL || node->type == T_VOID || node->type == T_IDENTIFIER
 			|| node->type == T_ARGUMENTS || node->type == T_ARGUMENT
-			|| node->type == T_STRUCT || node->type == T_FUNCTYPE || node->type == T_IMPORT || node->type == T_IMPORT_PATH || node->type == T_INTTYPE
+			|| node->type == T_STRUCT || node->type == T_FUNCTYPE || node->type == T_IMPORT 
+			|| node->type == T_IMPORT_PATH || node->type == T_INTTYPE || node->type == T_HANDLETYPE
 			|| node->type == T_FUNCPTR || node->type == T_FUNCBODY || node->type == T_FUNCTION
 			|| node->type == T_REPL_RESULT || node->type == T_DECLARE_GROUP) {
 	} else if (node->type == T_NUMERIC_LITERAL) {
@@ -2899,10 +2967,8 @@ bool ASTSetTypes(Tokenizer *tokenizer, Node *node) {
 			node->expressionType = node->firstChild->expressionType;
 		}
 	} else if (node->type == T_DECLARE) {
-		if (node->firstChild->sibling && node->firstChild && node->firstChild->type == T_ERR 
-				&& ASTMatching(node->firstChild->firstChild, node->firstChild->sibling->expressionType)) {
-			// Allow the implicit cast to an error type.
-			Node *cast = ASTImplicitCastToErr(tokenizer, node, node->firstChild->sibling);
+		if (node->firstChild->sibling && ASTImplicitCastIsPossible(node->firstChild, node->firstChild->sibling->expressionType)) {
+			Node *cast = ASTImplicitCastApply(tokenizer, node, node->firstChild, node->firstChild->sibling);
 			if (!cast) return false;
 			node->firstChild->sibling = cast;
 		} else if (node->firstChild->sibling && !ASTMatching(node->firstChild, node->firstChild->sibling->expressionType)) {
@@ -2967,10 +3033,8 @@ bool ASTSetTypes(Tokenizer *tokenizer, Node *node) {
 			return false;
 		}
 	} else if (node->type == T_EQUALS) {
-		if (node->firstChild->expressionType && node->firstChild->expressionType->type == T_ERR 
-				&& ASTMatching(node->firstChild->expressionType->firstChild, node->firstChild->sibling->expressionType)) {
-			// Allow the implicit cast to an error type.
-			Node *cast = ASTImplicitCastToErr(tokenizer, node, node->firstChild->sibling);
+		if (ASTImplicitCastIsPossible(node->firstChild->expressionType, node->firstChild->sibling->expressionType)) {
+			Node *cast = ASTImplicitCastApply(tokenizer, node, node->firstChild->expressionType, node->firstChild->sibling);
 			if (!cast) return false;
 			node->firstChild->sibling = cast;
 		} else if (!ASTMatching(node->firstChild->expressionType, node->firstChild->sibling->expressionType)) {
@@ -3032,10 +3096,8 @@ bool ASTSetTypes(Tokenizer *tokenizer, Node *node) {
 
 		if (node->type == T_RETURN) {
 			if (!ASTMatching(expressionType, returnType)) {
-				if (returnType && returnType->type == T_ERR 
-						&& ASTMatching(returnType->firstChild, node->firstChild->expressionType)) {
-					// Allow the implicit cast to an error type.
-					Node *cast = ASTImplicitCastToErr(tokenizer, node, node->firstChild);
+				if (ASTImplicitCastIsPossible(returnType, node->firstChild->expressionType)) {
+					Node *cast = ASTImplicitCastApply(tokenizer, node, returnType, node->firstChild);
 					if (!cast) return false;
 					node->firstChild = cast;
 				} else {
@@ -3205,19 +3267,26 @@ bool ASTSetTypes(Tokenizer *tokenizer, Node *node) {
 	} else if (node->type == T_COLON) {
 		Node *expressionType = node->firstChild->expressionType;
 
+		if (!expressionType) {
+			PrintError2(tokenizer, node, "This is not an expression.\n");
+			return false;
+		}
+
 		bool isList = expressionType->type == T_LIST;
 		bool isStr = expressionType->type == T_STR;
 		bool isFuncPtr = expressionType->type == T_FUNCPTR;
 		bool isErr = expressionType->type == T_ERR;
+		bool isInt = expressionType->type == T_INT;
+		bool isFloat = expressionType->type == T_FLOAT;
 
-		if (!isList && !isStr & !isFuncPtr && !isErr) {
+		if (!isList && !isStr & !isFuncPtr && !isErr && !isInt && !isFloat) {
 			PrintError2(tokenizer, node, "This type does not have any ':' operations.\n");
 			return false;
 		}
 
 		Token token = node->token;
 		Node *arguments[2] = { 0 };
-		bool returnsItem = false, returnsInt = false, returnsBool = false, returnsStr = false, simple = true;
+		bool returnsItem = false, returnsInt = false, returnsBool = false, returnsStr = false, returnsFloat = false, simple = true;
 		uint8_t op;
 
 		if (isList && KEYWORD("resize")) arguments[0] = &globalExpressionTypeInt, op = T_OP_RESIZE;
@@ -3233,6 +3302,8 @@ bool ASTSetTypes(Tokenizer *tokenizer, Node *node) {
 		else if (isList && KEYWORD("first")) returnsItem = true, op = T_OP_FIRST;
 		else if (isList && KEYWORD("last")) returnsItem = true, op = T_OP_LAST;
 		else if ((isList || isStr) && KEYWORD("len")) returnsInt = true, op = T_OP_LEN;
+		else if (isInt && KEYWORD("float")) returnsFloat = true, op = T_OP_INT_TO_FLOAT;
+		else if (isFloat && KEYWORD("truncate")) returnsInt = true, op = T_OP_FLOAT_TRUNCATE;
 		else if (isErr && KEYWORD("success")) returnsBool = true, op = T_OP_SUCCESS;
 		else if (isErr && KEYWORD("assert")) returnsItem = true, op = T_OP_ASSERT_ERR;
 		else if (isErr && KEYWORD("error")) returnsStr = true, op = T_OP_ERROR;
@@ -3357,6 +3428,7 @@ bool ASTSetTypes(Tokenizer *tokenizer, Node *node) {
 			node->expressionType = returnsItem ? expressionType->firstChild 
 				: returnsInt ? &globalExpressionTypeInt 
 				: returnsStr ? &globalExpressionTypeStr 
+				: returnsFloat ? &globalExpressionTypeFloat 
 				: returnsBool ? &globalExpressionTypeBool : NULL;
 		}
 
@@ -5289,6 +5361,14 @@ int ScriptExecuteFunction(uintptr_t instructionPointer, ExecutionContext *contex
 			context->c->stack[context->c->stackPointer - 2] = context->c->stack[context->c->stackPointer - 1];
 			context->c->stackIsManaged[context->c->stackPointer - 2] = context->c->stackIsManaged[context->c->stackPointer - 1];
 			context->c->stackPointer--;
+		} else if (command == T_OP_INT_TO_FLOAT) {
+			if (context->c->stackPointer < 1) return -1;
+			if (context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
+			context->c->stack[context->c->stackPointer - 1].f = context->c->stack[context->c->stackPointer - 1].i;
+		} else if (command == T_OP_FLOAT_TRUNCATE) {
+			if (context->c->stackPointer < 1) return -1;
+			if (context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
+			context->c->stack[context->c->stackPointer - 1].i = context->c->stack[context->c->stackPointer - 1].f;
 		} else if (command == T_PERSIST) {
 			if (!ExternalPersistWrite(context, NULL)) {
 				return 0;